是否可以在MVVM中拥有用户控件?

时间:2016-05-19 09:46:05

标签: wpf mvvm data-binding wpf-controls

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

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

所以我认为用户控件有一个用户控件内部逻辑的视图模型,即搜索当天的任务。因此,我将用户控件中日历的selectedDate属性绑定到用户控件的视图模型的属性,因此当值更改时,视图模型可以搜索当天的任务。

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

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

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

感谢。

修改

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

主要观点的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模式,它只是公开依赖属性以与主视图进行通信。

1 个答案:

答案 0 :(得分:1)

是。如果您使用使用导航系统在View / ViewModel之间移动的框架,则可以对其进行调整以启动UserControl View / ViewModel。如果视图是WindowUserControl,则无关紧要。

修改

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