Avalonia ReactiveUI:父VM中ObservableCollection更新后UI不刷新求助
问题:ReactiveUI中按钮点击后UI无法即时刷新
我创建了HomeViewModel作为父容器,它包含一个存储两个GameViewModel实例的ObservableCollection。每个GameViewModel对应一款游戏,带有切换文件夹监视器的按钮。按钮绑定的命令能正常执行,但点击后UI不会立即刷新,只有切换路由时才更新。
相关代码
HomeViewModel
public class HomeViewModel : ReactiveObject, IRoutableViewModel // Some other code private ObservableCollection<GameViewModel> _games = new() { new GameViewModel(new Game() { Name = "Game1", Type = GameType.Game1 }), new GameViewModel(new Game() { Name = "Gam2", Type = GameType.Game2 }), }; public ObservableCollection<GameViewModel> Games { get => _games; set => this.RaiseAndSetIfChanged(ref _games, value); }
HomeView.axaml
<ListBox Grid.Row="1" ItemsSource="{Binding Games}" MinWidth="250"> <ListBox.ItemsPanel> <ItemsPanelTemplate> <WrapPanel /> </ItemsPanelTemplate> </ListBox.ItemsPanel> </ListBox>
GameViewModel
public class GameViewModel : ViewModelBase { public GameViewModel() { } public GameViewModel(Game game) { Name = game.Name; Path = game.Path; Type = game.Type; SelectPath = ReactiveCommand.Create(SelectAnPath); ToggleWatcher = ReactiveCommand.Create(ToggleEnabled); } public ReactiveCommand<Unit, Unit> SelectPath { get; } public ReactiveCommand<Unit, Unit> ToggleWatcher { get; } private void SelectAnPath() { Debug.WriteLine("Path Clicked " + Name); Path = Guid.NewGuid().ToString().Substring(0, 10); } private void ToggleEnabled() { Debug.WriteLine("Watcher toggle click"); _isWatcherToggled = !_isWatcherToggled; } private string _name; public string Name { get => _name; set => this.RaiseAndSetIfChanged(ref _name, value); } private string _path; public string Path { get => _path; set => this.RaiseAndSetIfChanged(ref _path, value); } private GameType _type; public GameType Type { get => _type; set => this.RaiseAndSetIfChanged(ref _type, value); } private bool _isWatcherToggled; public bool IsWatcherToggled { get => _isWatcherToggled; set => this.RaiseAndSetIfChanged(ref _isWatcherToggled, value); } }
GameView.axaml
<StackPanel Width="200"> <Border CornerRadius="10" ClipToBounds="True"> <Panel Background="#7FFF22DD"> <!-- <Image Width="200" Stretch="Uniform" Source="{Binding Cover}" /> --> <!-- IsVisible="{Binding Cover, Converter={x:Static ObjectConverters.IsNull}}" --> <Panel Height="200"> <PathIcon Height="75" Width="75" Data="{StaticResource GamesRegular}"></PathIcon> </Panel> <Button VerticalAlignment="Bottom" HorizontalAlignment="Left" CornerRadius="10" Command="{Binding ToggleWatcher}" Classes.btn="{Binding IsWatcherToggled}"> <Button.Styles> <Style Selector="Button.btn"> <Setter Property="Background" Value="Bisque"></Setter> </Style> </Button.Styles> <PathIcon Height="25" Width="25" Data="{StaticResource PowerRegular}" Classes.toggled="{Binding IsWatcherToggled}" > <PathIcon.Styles> <Style Selector="PathIcon"> <Setter Property="Foreground" Value="Red"></Setter> </Style> <Style Selector="PathIcon.toggled"> <Setter Property="Foreground" Value="LimeGreen"></Setter> </Style> </PathIcon.Styles> </PathIcon> </Button> <Button VerticalAlignment="Bottom" HorizontalAlignment="Right" CornerRadius="10" Command="{Binding SelectPath}"> <PathIcon Height="25" Width="25" Data="{StaticResource FolderLinkRegular}"></PathIcon> </Button> </Panel> </Border> <TextBlock HorizontalAlignment="Center" Text="{Binding Name}" /> <TextBlock HorizontalAlignment="Center" /> </StackPanel>
MainWindowView
<Grid Margin="0, 25,0,0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="125" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <rxui:RoutedViewHost Grid.Column="1" Router="{Binding Router}" > <!-- Tela Mudavel aqui --> <rxui:RoutedViewHost.DefaultContent> <TextBlock Text="Default Content" HorizontalAlignment="Center" VerticalAlignment="Center" /> </rxui:RoutedViewHost.DefaultContent> <rxui:RoutedViewHost.ViewLocator> <!-- App View Locator --> <rhythmScrobbler:SimpleViewLocator /> </rxui:RoutedViewHost.ViewLocator> </rxui:RoutedViewHost> <StackPanel Grid.Column="0" Margin="5, 10, 0, 0" > <Button HorizontalAlignment="Center" Command="{Binding NavigateHome}"> Home </Button> <Separator /> </StackPanel> </Grid>
MainWindowViewModel
public class MainWindowViewModel : ReactiveObject, IScreen { // The Router associated with this Screen. // Required by the IScreen interface. public RoutingState Router { get; } = new RoutingState(); private ObservableCollection<IRoutableViewModel> ViewModelCollection { get; } public ReactiveCommand<Unit, IRoutableViewModel> NavigateHome { get; } public ReactiveCommand<Unit, IRoutableViewModel> NavigateLog { get; } public MainWindowViewModel() { ViewModelCollection = new ObservableCollection<IRoutableViewModel> { new HomeViewModel(this), //new SecondViewModel(this) // Add other view models here }; // NavigateCommand = ReactiveCommand.CreateFromTask<IRoutableViewModel>(NavigateToViewModel); NavigateHome = ReactiveCommand.CreateFromObservable( () => Router.Navigate.Execute(ViewModelCollection[0]) ); NavigateLog = ReactiveCommand.CreateFromObservable( () => Router.Navigate.Execute(new SecondViewModel(this)) ); Router.Navigate.Execute(ViewModelCollection[0]); } }
解决方案
问题核心在于GameViewModel的ToggleEnabled方法直接修改了私有字段_isWatcherToggled,没有触发ReactiveUI的属性变更通知。RaiseAndSetIfChanged只会在调用属性的setter时触发UI更新,直接操作字段无法让UI感知到状态变化。
修改ToggleEnabled方法,改用属性setter:
private void ToggleEnabled() { Debug.WriteLine("Watcher toggle click"); IsWatcherToggled = !IsWatcherToggled; }
同时确认ViewModelBase是否继承了ReactiveObject,如果没有,GameViewModel需要直接继承ReactiveObject,确保属性变更通知机制正常工作。
内容的提问来源于stack exchange,提问作者JulioG
相关产品推荐
相关产品推荐

