Álvaro García
Álvaro García

Reputation: 19366

Is it possible to have a user control in MVVM?

I was thinking to create a user control to be used in many applications, and I would like to use MVVM pattern.

For example, I have a user control with a calendar, that when I click in a day, the user control search for the tasks that I have to do this day.

So I was thinking that the user control has a view model for the logic inside the user control, that is to search for the tasks of the day. So I bind the selectedDate property of the calendar in the user control to the porperty to the view model of the user control, so when the value is changed, the view model can search for the tasks of the day.

Also I want that this user control notify to the main application, the selectedDate in the calendar, because the main aplication, has to do another things when the selected date is changed. So I have tried to bind a property in my main view model to the dependency property that I have created in my user control, but how in the property in the user control is bind to the property of the view model of the user control, the main view model is not notify when the day is changed.

I know how to do this in code behind, but I would like to know if it is possible to do in MVVM, because the user control has its own logic and I would like to follow the MVVM pattern. If not, when I have many user controls in my application, only the main application use the MVVM pattern and the rest code behind, so I could have a hugh percent of my application in code behind, and I would like to avoid this.

In summary, I would like to know how when I change a date in my calendar, the user control notify to its view model and also notify to the binding property in the main view in my application.

Thanks.

EDIT

Finally I have get what I wanted to do with events to communicate the changes in the view model of the user control to the code behind of the user control that updates the dependecy properties and the dependency properties notify the changes to the main view. The code is the following:

XAML of the main view:

<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>

The code of the main view model:

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
    }
}

The XAML of the user control:

<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>

The code behind of the user control, just to declare the dependency properties and notify the changes to and from the view model. The logic is in the view model.

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
    }
}

The view model of the user control:

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
    }
}

Finally, the class that implements the INotifyPropertyChanged, that can be any implementation that you prefer, but perhaps can be interesting for someone:

/*
 * 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 { };
    }
}

With this solution, we can see that if I change a date in the calendar, the first text box in the main view is updated to, but not the second text box because is not binded to the user control.

If I change the date in the first text box in the main view, the selected day in the calendar of the user control is updated too, but not the second text box.

If I change the date in the second text box, it is changed in the calendar beccause I update the selectedItemInUserControl property of the view model, and this property notify to the user control that changes inthe calendar.

So with this solution, I can have a MVVM patter inside the user control, that just expose dependency properties to communicate with the main view.

Upvotes: 0

Views: 1433

Answers (1)

Jonathan Twite
Jonathan Twite

Reputation: 952

Yes. If you use a framework that uses a navigation system to move between View/ViewModels, you could adapt this to launch your UserControl View/ViewModel. It doesn't matter if the view is a Window or UserControl.

EDIT

It is also possible to use a Messenger system (again available in most MVVM frameworks) to pass information between view models, so when a property in the control's ViewModel changes, it can send a message to the main ViewModel to change its property(s)

Upvotes: 1

Related Questions