在 DataTemplate 中绑定控件属性的最佳实践?

Best practice for binding properties of controls within a DataTemplate?

我看到多年来有很多关于如何最好地绑定数据模板中的数据的问题,是否有最佳实践?在这种情况下,我希望我的 MemoryCopyBtn 复制当前选定的 MemoryListItem TextBox 的文本,以便我可以在 ViewModel 中对其进行处理。

我可以使用 Findname 找到 ListView,但对于 PageLoad 上的 ListViewItem,它将显示 null。

我可以将所有内容都放在内存模型页面中,但我认为这不是最佳做法。

我看到有多种遍历可视化代码树的选项,但我想在运行时执行此操作,这真的是唯一的方法吗?

我有哪些选择? 谢谢。

<ListView x:Name="ClipboardList"
                      xmlns:m="using:QuickieEdit.Models"
                      ItemsSource="{x:Bind ViewModel.MemoryItems}">
                <ListView.ItemTemplate>
                    <DataTemplate x:DataType="m:MemoryItem">
                            <StackPanel Orientation="Horizontal">
                                <Button x:Name="MemoryCopyBtn"
                                             Content="Copy"
                                             Click="How to Copy currently selected
                                             MemoryListItem.Text?"/> 
                                <TextBox x:Name="MemoryListItem"
                                               Text="{x:Bind Memory, Mode=TwoWay}">
                               </TextBox>                                  
                           </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
</ListView>

如果想纯粹使用x:Bind,建议在XAML、Click="onButtonClicked"中定义Click事件。在 View 中,您调用 ViewModel(在 MVVM 中,View 知道 VM)或者您可以发布一条消息,ViewModel 将捕获它并完成工作。 MVVM Light 有这个 Messenger 机制。

如果我对你的理解是正确的——你想复制与你点击的按钮一起的文本框的文本,而不是一些 selected\focused 文本框(否则没有意义,因为你有一个复制按钮对于列表视图中的每个项目)。如果是这样,只需使用命令:

    <ListView.ItemTemplate>
        <DataTemplate x:DataType="m:MemoryItem">
            <StackPanel Orientation="Horizontal">
                <Button x:Name="MemoryCopyBtn"
                        Content="Copy"
                        Command="{Binding CopyCommand}"
                        CommandParameter="{Binding ElementName=MemoryListItem, Path=Text}" />
                <TextBox x:Name="MemoryListItem"
                         Text="{x:Bind Memory, Mode=TwoWay}"></TextBox>
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>

并且在您的 MemoryItem 中:

public class MemoryItem {
    public MemoryItem() {
        CopyCommand = new RelayCommand(Copy);
    }        

    public string Memory { get; set; }
    public ICommand CopyCommand { get; private set; }

    private void Copy(object argument)
    {
        var text = (string)argument;
        Console.WriteLine(text);
    }
}

我想你可以在你的MemoryItem中绑定Button的Click事件,然后你就可以在这个事件中获取被点击按钮的数据上下文

为了测试,我在这里写了一个非常简单的demo:

<ListView x:Name="ClipboardList" ItemsSource="{x:Bind ViewModel.MemoryItems}">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="local:MemoryItem">
            <StackPanel Orientation="Horizontal">
                <Button x:Name="MemoryCopyBtn"
                                     Content="Copy"
                                     Click="{x:Bind MemoryCopyBtn_Click}" />
                <TextBox x:Name="MemoryListItem"
                                       Text="{x:Bind Memory, Mode=TwoWay}">
                </TextBox>
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

后面的代码:

private MainPageViewModel ViewModel;

public MainPage()
{
    this.InitializeComponent();
    ViewModel = new MainPageViewModel();
}

我的 MainPageViewModel class:

public class MainPageViewModel
{
    public ObservableCollection<MemoryItem> MemoryItems;

    public MainPageViewModel()
    {
        MemoryItems = new ObservableCollection<MemoryItem>();
        MemoryItems.Clear();
        MemoryItems.Add(new MemoryItem { Memory = "Item 1" });
        MemoryItems.Add(new MemoryItem { Memory = "Item 2" });
        MemoryItems.Add(new MemoryItem { Memory = "Item 3" });
        MemoryItems.Add(new MemoryItem { Memory = "Item 4" });
        MemoryItems.Add(new MemoryItem { Memory = "Item 5" });
        MemoryItems.Add(new MemoryItem { Memory = "Item 6" });
        MemoryItems.Add(new MemoryItem { Memory = "Item 7" });
        MemoryItems.Add(new MemoryItem { Memory = "Item 8" });
    }
}

内存项class:

public class MemoryItem
{
    public string Memory { get; set; }

    public void MemoryCopyBtn_Click(object sender, RoutedEventArgs e)
    {
        var item = ((FrameworkElement)e.OriginalSource).DataContext as MemoryItem;
        var text = item.Memory;
    }
}

此处的代码与您的代码并非100%相同,非常简单,仅供测试,请不要介意。通常情况下,我们避免使用 VisualTree Helper 来完成这项工作,x:Bind 可以用于事件,它在这里非常有用。