【问题标题】:Is it possible to have a user control in MVVM?是否可以在 MVVM 中进行用户控制?
【发布时间】:2016-05-19 09:46:04
【问题描述】:

我正在考虑创建一个用于许多应用程序的用户控件,并且我想使用 MVVM 模式。

例如,我有一个带有日历的用户控件,当我点击一天时,用户控件会搜索我这一天必须完成的任务。

所以我在想用户控件有一个视图模型用于用户控件内部的逻辑,即搜索当天的任务。所以我将用户控件中日历的 selectedDate 属性绑定到用户控件的视图模型的 porperty 上,这样当值发生变化时,视图模型就可以搜索当天的任务了。

我还希望这个用户控件通知主应用程序,即日历中的 selectedDate,因为主应用程序必须在所选日期更改时执行其他操作。因此,我尝试将主视图模型中的属性绑定到我在用户控件中创建的依赖项属性,但是如何将用户控件中的属性绑定到用户控件的视图模型的属性,更改日期时不会通知主视图模型。

我知道如何在后面的代码中执行此操作,但我想知道是否可以在 MVVM 中执行此操作,因为用户控件有自己的逻辑,我想遵循 MVVM 模式。如果没有,当我的应用程序中有很多用户控件时,只有主应用程序使用 MVVM 模式,其余代码在后面,所以我的应用程序中有很大一部分代码在后面,我想避免这种情况。

总之,我想知道当我更改日历中的日期时,用户控件如何通知其视图模型并通知我应用程序主视图中的绑定属性。

谢谢。

编辑

最后,我得到了我想要做的事情,将用户控件的视图模型中的更改传达给更新依赖属性的用户控件背后的代码,并且依赖属性通知主视图的更改.代码如下:

主视图的 XAML:

<Window x:Class="UserControlMvvm.MainView"
        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:UserControlMvvm"
        xmlns:vm="clr-namespace:UserControlMvvm"
        mc:Ignorable="d"
        Name="_mainView"
        Title="MainView" Height="350" Width="525">

    <Window.DataContext>
        <vm:MainViewModel />
    </Window.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="102*"/>
            <RowDefinition Height="217*"/>
        </Grid.RowDefinitions>

        <local:ucMyUserControlView HorizontalAlignment="Center" Margin="0,0,0,0" Grid.Row="1" VerticalAlignment="Center"
                                   SelectedDate="{Binding ElementName=_mainView, Path=DataContext.SelectedDateInUserControl, Mode=TwoWay}"/>

        <TextBox x:Name="txtSelectedDateInUserControl" Text="{Binding SelectedDateInUserControl}" HorizontalAlignment="Left" Height="23" Margin="10,35,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>

        <Label x:Name="lblSelectedDate" Content="SelectedDate in UserControl" HorizontalAlignment="Left" Margin="10,0,0,0" VerticalAlignment="Top"/>

        <TextBox x:Name="txtSelectedDateToUserControl" HorizontalAlignment="Right" Height="23" Margin="0,35,5,0" TextWrapping="Wrap" Text="{Binding SelectedDateToUserControl, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="120"/>

        <Label x:Name="lblSeelectedDateToUserControl" Content="Change date on user control" HorizontalAlignment="Right" Margin="0,0,5,0" VerticalAlignment="Top"/>

    </Grid>
</Window>

主视图模型的代码:

using System;


namespace UserControlMvvm
{
    class MainViewModel : ViewModelBase
    {
        #region properties
        private DateTime? _selectedDateInUserControl;
        public DateTime? SelectedDateInUserControl
        {
            get { return _selectedDateInUserControl; }
            set
            {
                if(_selectedDateInUserControl != value)
                {
                    SetProperty(ref _selectedDateInUserControl, value);
                    selectedDateInUserControlChanged();
                }
            }
        }

        private string _selectedDateInUserControlText;
        public string SelectedDateInUserControlText
        {
            get { return _selectedDateInUserControlText; }
            set
            {
                if (_selectedDateInUserControlText != value)
                {
                    SetProperty(ref _selectedDateInUserControlText, value);
                }
            }
        }

        private string _selectedDateToUserControl;
        public string SelectedDateToUserControl
        {
            get { return _selectedDateToUserControl; }
            set
            {
                if (_selectedDateToUserControl != value)
                {
                    SetProperty(ref _selectedDateToUserControl, value);
                    DateTime miDateParsed;
                    DateTime.TryParse(value, out miDateParsed);
                    SelectedDateInUserControl = miDateParsed;
                }
            }
        }
        #endregion properties



        #region methods
        /// <summary>
        /// This method is used to do all the tasks needed when the selectedDate in the user control is changed.
        /// </summary>
        private void selectedDateInUserControlChanged()
        {
            try
            {
                //here the code that the main view model has to do when the selected date is changed in the user control.
            }
            catch
            {
                throw;
            }
        }//selectedDateInUserControlChanged
        #endregion methods
    }
}

用户控件的 XAML:

<UserControl x:Class="UserControlMvvm.ucMyUserControlView"
             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:UserControlMvvm"
             mc:Ignorable="d"
             Name="_ucMyUserControl"
             Width="Auto" Height="Auto">
    <Grid>
        <Calendar HorizontalAlignment="Center" Margin="0,0,0,0" VerticalAlignment="Center"
                  SelectedDate="{Binding ElementName=_ucMyUserControl,Path=DataContext.SelectedDate, Mode=TwoWay}"/>

    </Grid>
</UserControl>

用户控件背后的代码,仅用于声明依赖属性并通知视图模型的更改。逻辑在视图模型中。

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


namespace UserControlMvvm
{
    /// <summary>
    /// Interaction logic for ucMyUserControl.xaml
    /// </summary>
    public partial class ucMyUserControlView : UserControl
    {
        ucMyUserControlViewModel _viewModel;

        public ucMyUserControlView()
        {
            InitializeComponent();

            //The view model is needed to be instantiate here, not in the XAML, because if not, you will get a null reference exception
            //because you try to access to a property when the view model is not still instantiate.
            _viewModel = new ucMyUserControlViewModel();
            DataContext = _viewModel;

            //Events
            _viewModel.SelectedDateChangedEvent += selectedDateChanged;
        }




        #region dependency properties
        //This are the properties that the main view will have available when will use the user control, so dependency properties are the
        //communication way between the main view and the user control.
        //So here you have to declare all the properties that you want to expose to outside, to the main view.

        public static readonly DependencyProperty SelectedDateProperty =
            DependencyProperty.Register("SelectedDate", typeof(DateTime?),
                typeof(ucMyUserControlView), new PropertyMetadata(null, selectedDateChanged));
        public DateTime? SelectedDate
        {
            get
            {
                return (DateTime?)GetValue(SelectedDateProperty);
            }
            set
            {
                //This is the way in which the user control notify to the main view that the selected date is changed.
                SetValue(SelectedDateProperty, value);
            }
        }

        private static void selectedDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            //This is the way in which the code behind notify to the view model that the main view has changed by the main view.
            ((ucMyUserControlView)d)._viewModel.SelectedDate = e.NewValue as DateTime?;
        }
        #endregion dependency properties



        #region methods to receive notifications from the view model
        //These are the methods that are subcribed to the events of the view model, to know when a property has changed in the view
        //model and be able to notify to the main view.
        private void selectedDateChanged(DateTime? paramSelectedDate)
        {
            try
            {
                //This update the dependency property, so this notify to the main main that the selected date has been changed in the
                //user control.
                SetValue(SelectedDateProperty, paramSelectedDate);
            }
            catch
            {
                throw;
            }
        }//selectedChanged
        #endregion methods to receive notificactions from the view model
    }
}

用户控件的视图模型:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UserControlMvvm
{
    class ucMyUserControlViewModel : ViewModelBase
    {
        #region events
        //The events are user to notify changes from the properties in this view model to the code behind of the user control, so
        //later the user control can notify the changes from the code behind to the main view.
        //This is because the user control only can notify changes to the main view from the dependency properties and the dependency properties
        //are declared in the code behind. So to communicate changes from the view model to the code behind it is needed the use of an event.

        //So the changes are notify in this way:
        //view model --> code behind --> main view

        public delegate void SelectedDateChangedEventHandler(DateTime? paramSelectedDate);
        public event SelectedDateChangedEventHandler SelectedDateChangedEvent;


        private void OnSelectedDateChanged(DateTime? paramTipoSeleccionado)
        {
            try
            {
                //Here notify to the code behind of the user control that the selectedDate is changed.
                SelectedDateChangedEvent?.Invoke(paramTipoSeleccionado);
            }
            catch
            {
                throw;
            }
        }//OnSelectedDateChanged
        #endregion events



        #region properties
        private DateTime? _selectedDate;
        public DateTime? SelectedDate
        {
            get { return _selectedDate; }
            set
            {
                if(_selectedDate != value)
                {
                    SetProperty(ref _selectedDate, value);
                    selectedDateChanged();
                    OnSelectedDateChanged(SelectedDate);
                }
            }
        }
        #endregion properties



        #region methods
        private void selectedDateChanged()
        {
            try
            {
                //Here the code that the user control has to execute when the selectedDate is changed.
            }//try
            catch
            {
                throw;
            }
        }
        #endregion methods
    }
}

最后,实现 INotifyPropertyChanged 的​​类,它可以是您喜欢的任何实现,但也许对某人来说可能很有趣:

/*
 * Class that implements the INotifyPropertyChanged that it is used by all view models to
 * notifiy changes in their properties to the view.
 */

using System.Runtime.CompilerServices;
using System.ComponentModel;

namespace UserControlMvvm
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        protected virtual void SetProperty<T>(ref T member, T val,
            [CallerMemberName] string propertyName = null)
        {
            if (object.Equals(member, val)) return;

            member = val;
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        public event PropertyChangedEventHandler PropertyChanged = delegate { };
    }
}

通过这个解决方案,我们可以看到,如果我更改日历中的日期,主视图中的第一个文本框会更新为,但第二个文本框不会更新,因为没有绑定到用户控件。

如果我在主视图的第一个文本框中更改日期,用户控件日历中选定的日期也会更新,但不会更新第二个文本框。

如果我更改第二个文本框中的日期,它会在日历中更改,因为我更新了视图模型的 selectedItemInUserControl 属性,并且此属性会通知用户控件日历中的更改。

因此,使用此解决方案,我可以在用户控件中拥有一个 MVVM 模式,它只公开依赖属性以与主视图进行通信。

【问题讨论】:

  • 据我了解,这是因为英语不是我的第一语言,您可以将日历SelectedDate 绑定到主视图模型和您的应用程序以查找视图模型中的更改。因此,您应该将 App 和 UserControl 都连接到 ViewModel,而不是连接 App->UserControl->ViewModel。
  • 好问题。 +1 我一直想知道这种事情是否可能。即具有视图模型的可重用控件。但我认为不可能实现它。也许您可以在不同的地方为可重用控件设置单独的视图模型,而不是单独为特定控件设置视图模型。在正常情况下,我们在代码后面编写代码并使用依赖属性和这些属性更改时的操作来实现可重用性。
  • @Vishakh369:对,在代码隐藏和依赖属性中很容易做到,我有几个简单的用户控件可以通过两种方式通知更改,从主应用程序到用户控件以及从用户控件到主要应用程序。但是这个用户控件没有内部逻辑。但是当你想要一个内部逻辑时,如果你的主应用程序使用了很多这样的控件,最后你有一个主应用程序使用 MVVM,但是如果用户控件是应用程序的 50%,会发生什么?然后我将只有 50% 的应用程序带有 MVVM。以及如何对我的用户控件进行单元测试?
  • 在做了一些研究之后,我意识到为可重用控件提供代码并不违反 MVVM。可以在可重用控件的代码后面限制代码。可能将其限制为单独的依赖属性或后面代码中的极少代码,并在放置控件的父 VM 中包含该功能,或为该可重用控件的所需通用功能编写一些新类,并在所需 VM 中创建其实例.阅读此stackoverflow.com/questions/21187100/…

标签: wpf mvvm data-binding wpf-controls


【解决方案1】:

是的。如果您使用使用导航系统在 View/ViewModel 之间移动的框架,您可以调整它以启动您的 UserControl View/ViewModel。视图是Window 还是UserControl 都没有关系。

编辑

也可以使用Messenger 系统(在大多数 MVVM 框架中同样可用)在视图模型之间传递信息,因此当控件的 ViewModel 中的属性发生更改时,它可以向主 ViewModel 发送消息以进行更改它的属性

【讨论】:

  • 我想你误解了 Álvaro García 的问题。
猜你喜欢
  • 1970-01-01
  • 2019-04-21
  • 2023-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-20
  • 2011-01-01
  • 2019-05-25
相关资源
最近更新 更多