【问题标题】:WPF Can I use a DataTrigger to have the View do something after the View Model changes the value of a Property?WPF 我可以使用 DataTrigger 让视图在视图模型更改属性值后执行某些操作吗?
【发布时间】:2023-01-28 00:38:26
【问题描述】:

我的 WPF MVVM VB.NET 应用程序在开始时将歌曲列表加载到 ListBox 中。列表内容填充在 BackgroundWorker 中,该 BackgroundWorker 在 ViewModel 的构造函数中启动。完成后,我想将焦点设置到列表中的第一首歌曲。

由于设置此焦点纯粹是一个 View 操作,我希望它位于 XAML 的代码隐藏中。焦点所在与 ViewModel 无关。

我尝试在各种 Window 和 ListBox 事件上执行此操作,但它们要么不触发,要么触发得太早。所以我认为我需要的是 ViewModel 在将歌曲加载到列表中后设置的布尔属性。那时我需要 View 来捕获该属性更改,并调用具有操作 View 逻辑的代码隐藏函数,在这种情况下,将焦点设置在列表中的第一首歌曲上。

但这是我对 WPF 的了解不足的地方。我搜索了一下,听起来 DataTrigger 可以解决问题。但是把它放在哪里,正确的语法是什么,以及如何让它调用我的代码隐藏函数?

还是我忽略了一种更简单的方法。这似乎是一个基本功能——当属性在 ViewModel 中以某种方式更改时,在视图中触发一些代码隐藏操作。

这是代码隐藏函数。一旦在预定时间成功调用它,我就可以详细说明它:

Private Sub FocusSongsList()
    ' set focus back to the Songs list, selected item (couldn't just set focus to the list, it ran forever and looks like it set focus to every item in turn before releasing the UI)
    Dim listBoxItem = CType(LstSongs.ItemContainerGenerator.ContainerFromItem(LstSongs.SelectedItem), ListBoxItem)
    If Not listBoxItem Is Nothing Then
        listBoxItem.Focus()
    End If
End Sub

这是我的列表框:

<ListBox x:Name="LstSongs" ItemsSource="{Binding FilteredSongs}" DisplayMemberPath="Path" 
            HorizontalAlignment="Stretch"
            SelectionMode="Extended" SelectionChanged="LstSongs_SelectionChanged" Loaded="FocusSongsList"/>

我将定义一个新属性,该属性可以从 BackgroundWorker 的 RunWorkerCompleted 部分进行设置。

Private _InitialSongLoadCompleted As Boolean
Public Property InitialSongLoadCompleted() As Boolean
    Get
        Return _InitialSongLoadCompleted
    End Get
    Set(ByVal value As Boolean)
        _InitialSongLoadCompleted = value
        RaisePropertyChanged("InitialSongLoadCompleted")
    End Set
End Property

【问题讨论】:

  • 这是一次性的事情,还是您在第一次展示后更换过清单?
  • 您是否考虑过将自定义事件放在您的视图模型上并在您的视图中订阅它?我通常认为数据触发器是纯粹的 XAML 领域的东西。

标签: wpf vb.net mvvm


【解决方案1】:

我已经很久没有写过很多 VB 了,所以恐怕这是 C# 代码。

您可以在绑定上处理 targetupdated。

当数据从源(viewmodel 属性)传输到目标(ui 属性和此处的 itemssource)时会触发

    <ListBox 
        x:Name="LstSongs"
        ItemsSource="{Binding Songs, NotifyOnTargetUpdated=True}"
        TargetUpdated="ListBox_TargetUpdated"/>

当您替换列表时,该 targetupdated 将触发。 如果您提出属性更改,那么数据将传输(显然)。

    private async void ListBox_TargetUpdated(object sender, DataTransferEventArgs e)
    {
        await Task.Delay(200);
        var firstItem = (ListBoxItem)LstSongs.ItemContainerGenerator
                          .ContainerFromItem(LstSongs.Items[0]);
        firstItem.Focus();
        Keyboard.Focus(firstItem);
    }

随着数据传输,最初当然不会有任何项目,所以我们需要一点延迟。因此,Task.Delay 将等待 200 毫秒并且应该让 UI 呈现。你可以让它更长一点或 dispatcher.invokeasync。

它找到第一个容器并设置焦点和键盘焦点。 item 具有焦点可能一点都不明显。

使用调度程序的更优雅的方法将有效地安排此焦点,直到 ui 呈现之后。然而,对于不熟悉 C# 的人来说,它可能看起来相当棘手

    private void ListBox_TargetUpdated(object sender, DataTransferEventArgs e)
    {
        Dispatcher.CurrentDispatcher.InvokeAsync(new Action(() =>
        {
            var firstItem = (ListBoxItem)LstSongs.ItemContainerGenerator
                              .ContainerFromItem(LstSongs.Items[0]);
            firstItem.Focus();
            Keyboard.Focus(firstItem);
        }), DispatcherPriority.ContextIdle);
    }

这是我的主窗口视图模型

public partial class MainWindowViewModel : ObservableObject
{
    [ObservableProperty]
    private List<string> songs = new List<string>();

    MainWindowViewModel()
    {
        Task.Run(() => { SetupSongs(); });
    }

    private async Task SetupSongs()
    {
        await Task.Delay(1000);
        Songs = new List<string> { "AAA", "BBB", "CCC" };
    }
}

我正在使用社区工具包 mvvm 进行代码生成。也许它做 vb 和 c#。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-03-15
    • 2020-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-28
    • 2012-05-28
    相关资源
    最近更新 更多