You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 10:18:11