【问题标题】:WPF - How to handle event raised from UserControl in MainWindow and update ViewModelWPF - 如何处理从 MainWindow 中的 UserControl 引发的事件并更新 ViewModel
【发布时间】:2017-11-21 06:14:04
【问题描述】:

我试图在 UserControl1 中引发一个事件,但我不知道如何处理该事件以在 MainWindow 中显示 UserControl2。

MainWindow.xaml

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">

    <Window.DataContext>
        <local:ViewModel1 />
    </Window.DataContext>

    <Window.Resources>
        <DataTemplate x:Key="Template1" DataType="{x:Type local:ViewModel1}">
            <local:UserControl1 />
        </DataTemplate>
        <DataTemplate x:Key="Template2" DataType="{x:Type local:ViewModel1}">
            <local:UserControl2 />
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ContentControl Content="{Binding }">
            <ContentControl.Style>
                <Style TargetType="{x:Type ContentControl}">
                    <Setter Property="ContentTemplate" Value="{StaticResource Template1}" />
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding UserControlId}" Value="2">
                            <Setter Property="ContentTemplate" Value="{StaticResource Template2}" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </ContentControl.Style>
        </ContentControl>
    </Grid>
</Window>

MainWindow.cs

using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

UserControl1.xaml

<UserControl x:Class="WpfApp1.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Label Content="User Control 1" />
        <Button x:Name="btnNext" Content="Next" Click="btnNext_Click"></Button>
    </Grid>
</UserControl>

UserControl1.cs

using System.Windows;
using System.Windows.Controls;

namespace WpfApp1
{
    public partial class UserControl1 : UserControl
    {
        public static readonly RoutedEvent MyEvent = EventManager.RegisterRoutedEvent(
            "MyEventName",
            RoutingStrategy.Bubble,
            typeof(RoutedEventHandler),
            typeof(UserControl1)
        );

        public event RoutedEventHandler LoginEventHandler
        {
            add { AddHandler(MyEvent, value); }
            remove { RemoveHandler(MyEvent, value); }

        }
        public UserControl1()
        {
            InitializeComponent();
        }

        private void btnNext_Click(object sender, RoutedEventArgs e)
        {
            var eventArgs = new RoutedEventArgs(MyEvent);
            RaiseEvent(eventArgs);
        }
    }
}

UserControl2.xaml

<UserControl x:Class="WpfApp1.UserControl2"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Label Content="User Control 2" />
    </Grid>
</UserControl>

UserControl2.cs

using System.Windows.Controls;

namespace WpfApp1
{
    public partial class UserControl2 : UserControl
    {
        public UserControl2()
        {
            InitializeComponent();
        }
    }
}

ViewModel1.cs

using System.ComponentModel;

namespace WpfApp1
{
    public class ViewModel1 : INotifyPropertyChanged
    {
        public int UserControlId { get; set; }

        public ViewModel1()
        {
            UserControlId = 2;
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

我想我必须更新 ViewModel1 中的 UserControlId 属性来更改用户控件,但是在哪里处理以及如何获取 ViewModel1 实例来更改属性?

编辑 1 抱歉 ViewModel1 构造函数中的拼写错误,我打算将 UserControlId 属性初始化为 1,而不是 2。

当我运行它时,我看到在主窗口中加载了 UserControl1 的窗口。 UserControl1 有一个“下一步”按钮。 单击 UserControl1 中的按钮时,我想显示 UserControl2。 所以我在按钮单击事件处理程序中引发 MyEvent 路由事件。 现在,在哪里附加和编写 MyEvent 的事件处理程序以及如何获取 ViewModel1 实例来更改它的 UserControlId 属性?

【问题讨论】:

标签: wpf


【解决方案1】:

我终于让它工作了。引发的事件在 ViewModel1 中更改为属性名称,在 MainWindow 中添加了事件侦听器,

这是修改后的文件

ViewModel1.cs

using System.ComponentModel;

namespace WpfApp1
{
    public class ViewModel1 : INotifyPropertyChanged
    {
        private int _userControlId;

        public int UserControlId { 
            get { return _userControlId; }

            set 
            {
                _userControlId = value;
                RaisePropertyChanged("UserControlId");
            }
        }

        private void RaisePropertyChanged(string v = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(v));
        }

        public ViewModel1()
        {
            UserControlId = 1;
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

MainWindow.cs

using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            AddHandler(UserControl1.MyEvent, new RoutedEventHandler(OnMyEvent));
        }

        private void OnMyEvent(object sender, RoutedEventArgs e)
        {
            var vm = (ViewModel1)DataContext;
            vm.UserControlId = "2";
        }
    }
}

【讨论】:

  • 非常感谢@smk
【解决方案2】:

不要认为这里需要路由事件。将 ViewModel 分配给 MainWindow,并在 MainViewModel 中添加属性“UserControlId”。然后,您的 XAML 代码将正常工作,因为您已在 MainWindow 的 DataContext 级别对 UserControlId 的值编写了触发器。

【讨论】:

  • "将 ViewModel 分配给 MainWindow" - 我已经这样做了。触发器会改变 UserControl 但如何触发呢?
  • 从您提供的代码中,我可以看到属性“UserControlId”在“ViewModel1”内,这就是 DataTrigger 没有正确绑定属性的原因。它应该在 MainViewModel 中,在单击“下一步”按钮时也将属性的值更改为“2”。
【解决方案3】:

为了做到这一点,您需要在两个视图模型之间进行调解。一种调解方法是让主窗口成为调解者:它侦听来自子级的事件,然后将它们路由到所需的目的地。另一种方式是通过事件聚合器。大多数 MVVM 框架都有一个。我所知道的 WPF 世界中最好的开源之一是Caliburn Micro 中的那个。您在一个视图模型中订阅某种消息,然后从其他视图模型发送事件,事件聚合器确保事件被路由到其目的地。有关详细信息,请参阅他们的文档。

【讨论】:

  • " 它监听来自孩子的事件,然后将它们路由到他们需要的目的地。" - 你能指点我做这件事的任何资源吗?
  • 是的,我添加了链接。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-08-11
  • 1970-01-01
  • 1970-01-01
  • 2017-07-25
  • 2010-12-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多