【问题标题】:WPF Event Binding to ViewModel (for non-Command classes)WPF 事件绑定到 ViewModel(对于非命令类)
【发布时间】:2011-09-19 18:58:33
【问题描述】:

我正在开发应用程序的第二个版本,作为重写的一部分,我必须迁移到 MVVM 架构。我正在承受将所有代码都放入视图模型类的压力——在文件背后的代码中包含 c# 是不受欢迎的。 (我知道,我知道...我知道背后的代码不是一件坏事,但这次不是我的决定)。

对于实现命令接口的对象,这很容易。我已经找到了大量关于如何将这些对象的 Command 绑定到视图模型中的 ICommand 的信息。问题在于没有此接口的对象,例如

<ListBox
   x:Name="myListBox"
   MouseDoubleClick="myCallbackFunction">

<!-- ... -->

</ListBox>

我想知道如何将 Listbox 的 MouseDoubleClick 事件绑定到 myCallbackFunction,这是在视图模型中实现的。这甚至可能吗?

谢谢!

【问题讨论】:

标签: c# xaml mvvm binding viewmodel


【解决方案1】:

这是不可能的。它可以通过附加属性或行为来完成,尽管找到和调用适当的方法仍然有点棘手(这可以通过反射很容易地完成)。

话虽如此,这通常通过ICommand 处理 - 例如,MVVM Light 具有出色的EventToCommand 行为,可以将任何事件映射到 ViewModel 上的 ICommand。使用 ICommand 的优点是您仍然可以使用 DataBinding,因为 ICommand 是作为属性公开的。

【讨论】:

    【解决方案2】:

    WPF 从 .NET 4.5 开始支持事件的标记扩展。使用该功能,我实现了一个通用的方法绑定扩展,并在这里写了:

    http://www.singulink.com/CodeIndex/post/building-the-ultimate-wpf-event-method-binding-extension

    可用于使用完整的属性路径语法绑定到方法,支持绑定和其他标记扩展作为参数,并自动路由到与提供的参数签名匹配的方法。以下是一些用法示例:

    <!--  Basic usage  -->
    <Button Click="{data:MethodBinding OpenFromFile}" Content="Open" />
    
    <!--  Pass in a binding as a method argument  -->
    <Button Click="{data:MethodBinding Save, {Binding CurrentItem}}" Content="Save" />
    
    <!--  Another example of a binding, but this time to a property on another element  -->
    <ComboBox x:Name="ExistingItems" ItemsSource="{Binding ExistingItems}" />
    <Button Click="{data:MethodBinding Edit, {Binding SelectedItem, ElementName=ExistingItems}}" />
    
    <!--  Pass in a hard-coded method argument, XAML string automatically converted to the proper type  -->
    <ToggleButton Checked="{data:MethodBinding SetWebServiceState, True}"
                    Content="Web Service"
                    Unchecked="{data:MethodBinding SetWebServiceState, False}" />
                    
    <!--  Pass in sender, and match method signature automatically -->
    <Canvas PreviewMouseDown="{data:MethodBinding SetCurrentElement, {data:EventSender}, ThrowOnMethodMissing=False}">
        <controls:DesignerElementTypeA />
        <controls:DesignerElementTypeB />
        <controls:DesignerElementTypeC />
    </Canvas>
    
        <!--  Pass in EventArgs  -->
    <Canvas MouseDown="{data:MethodBinding StartDrawing, {data:EventArgs}}"
            MouseMove="{data:MethodBinding AddDrawingPoint, {data:EventArgs}}"
            MouseUp="{data:MethodBinding EndDrawing, {data:EventArgs}}" />
    
    <!-- Support binding to methods further in a property path -->
    <Button Content="SaveDocument" Click="{data:MethodBinding CurrentDocument.DocumentService.Save, {Binding CurrentDocument}}" />
    

    查看模型方法签名:

    public void OpenFromFile();
    public void Save(DocumentModel model);
    public void Edit(DocumentModel model);
    
    public void SetWebServiceState(bool state);
    
    public void SetCurrentElement(DesignerElementTypeA element);
    public void SetCurrentElement(DesignerElementTypeB element);
    public void SetCurrentElement(DesignerElementTypeC element);
    
    public void StartDrawing(MouseEventArgs e);
    public void AddDrawingPoint(MouseEventArgs e);
    public void EndDrawing(MouseEventArgs e);
    
    public class Document
    {
        // Fetches the document service for handling this document
        public DocumentService DocumentService { get; }
    }
    
    public class DocumentService
    {
        public void Save(Document document);
    }
    

    【讨论】:

      【解决方案3】:

      要直接回答您的问题,请参考Why to avoid the codebehind in WPF MVVM pattern? 它建议了您想要的两个可能的东西。

      但是,为什么要将 ListBox 的 MouseDoubleClick 绑定到 viewmodel 中的 ICommand?

      另一种方法是在代码隐藏中编写一个方法来注册 MouseDoubleClick。由于以下事实,这还不错。

      1. 有意义的数据绑定是视图和视图模型之间的交互。例如,当用户向 TextBox 输入一些文本时,视图模型也会更新。相反,如果视图模型从数据库中获取数据,它将显示在视图中。但是,视图模型中的 ICommand 不会绑定到视图。

      2. 当然,ICommand 的 CanExcute 对您的视图模型很重要,但在许多情况下,它与视图模型无关或无关。在这种情况下,ICommand 绑定和编写代码隐藏之间的区别在于 MouseDoubleClick 事件与 ICommand 绑定或注册到事件处理程序。

      【讨论】:

      • ListBox 上的 MouseDoubleClick 事件只是遗留代码中存在的一个示例,我可能不会保留该特定事件。但是,我想知道是否有办法避免此类事件背后的代码。我的代码目前在代码中有一个简单的事件处理程序,它调用视图模型中的方法。但由于我办公室的政治原因,我被指示找到一种方法来避免在后面的代码中编写代码。
      • 作为我的答案的顶部和上面提到的@Reed Copsey,您想要使用两种可能的东西,即附加属性或行为。它们在my question 进行了描述。但是,我想知道为什么鼓励您避免在后面的代码中编写代码。我认为@slugster 对my question 的回答会对您有所帮助。
      • 理由是业务逻辑需要与 HMI 完全分离(我正在与“纯粹主义者”合作)。但是,我已经尝试了您上面链接的解决方案,但我还没有能够让任何一个工作。每个潜在的解决方案都没有简单地使用背后的代码作为中介那么优雅。我决定坚持使用我的原始代码,看看是否可以在下一次同行评审中通过。
      【解决方案4】:

      一种方法是在后面的代码中处理事件并从后面的代码中调用视图模型的适当方法

      您还可以使用一些现成的命令库,例如使用 ACBthis 教程

      【讨论】:

        【解决方案5】:

        试试 EventBinder,它允许您将方法直接绑定到任何事件,包括您自己的事件,而无需将方法包装在 ICommand 容器中。

        https://github.com/Serg046/EventBinder
        https://www.nuget.org/packages/EventBinder

        .NET Framework 3.0 + 和 .NET Core 3.0 + 支持

        特点:

        • 绑定到没有 ICommand 的方法
        • 绑定到返回类型的方法
        • 绑定到异步方法
        • 使用. 分隔符、属性和字段绑定到嵌套对象 支持
        • 传递 int、double、decimal 或 string 类型的用户参数
        • 使用$符号和位置编号传递事件参数($0$1, 等)
        • 将默认 {Binding} 作为参数传递

        用法:

        public class ViewModel
        {
            public MetadataViewModel Metadata { get; } = new MetadataViewModel();
        
            public async Task ShowMessage(string msg, decimal centenary, double year)
            {
                await Task.Delay(0);
                MessageBox.Show(msg + centenary + year);
            }
        
            public class MetadataViewModel
            {
                public void ShowInfo(Window window, double windowWidth, ViewModel viewModel, object sender, MouseButtonEventArgs eventArgs)
                {
                    var sb = new StringBuilder("Window width: ")
                        .AppendLine(windowWidth.ToString())
                        .Append("View model type: ").AppendLine(viewModel.GetType().Name)
                        .Append("Sender type: ").AppendLine(sender.GetType().Name)
                        .Append("Clicked button: ").AppendLine(eventArgs.ChangedButton.ToString())
                        .Append("Mouse X: ").AppendLine(eventArgs.GetPosition(window).X.ToString())
                        .Append("Mouse Y: ").AppendLine(eventArgs.GetPosition(window).Y.ToString());
                    MessageBox.Show(sb.ToString());
                }
            }
        }
        

        绑定:

        <Window xmlns:e="clr-namespace:EventBinder;assembly=EventBinder" Name="Wnd">
            <Rectangle Fill="LightGray" Name="Rct"
                MouseLeftButtonDown="{e:EventBinding ShowMessage, `Happy `, 20m, 20.0 }"
                MouseRightButtonDown="{e:EventBinding Metadata.ShowInfo, {Binding ElementName=Wnd},
                    {Binding ElementName=Wnd, Path=ActualWidth}, {Binding}, $0, $1 }" />
        </Window>
        

        EventBinding.Bind(Rct, nameof(Rct.MouseLeftButtonDown),
            nameof(ViewModel.ShowMessage),
            "`Happy `", 20m, 20.0);
        EventBinding.Bind(Rct, nameof(Rct.MouseRightButtonDown),
            nameof(ViewModel.Metadata) + "." + nameof(ViewModel.Metadata.ShowInfo),
            new Binding { ElementName = nameof(Wnd)},
            new Binding("ActualWidth") { ElementName = nameof(Wnd) },
            new Binding(),
            "$0", "$1");
        

        【讨论】:

          猜你喜欢
          • 2011-06-21
          • 2012-02-23
          • 1970-01-01
          • 1970-01-01
          • 2012-06-17
          • 2011-11-07
          • 1970-01-01
          • 2012-06-11
          • 2014-11-06
          相关资源
          最近更新 更多