我正在考慮創建一個用戶控件以用於許多應用程序,並且我想使用MVVM模式。是否有可能在MVVM中擁有用戶控件?
例如,我有一個帶有日曆的用戶控件,當我在一天中點擊時,用戶控制搜索我今天必須執行的任務。
所以我在想用戶控件具有用戶控件內部邏輯的視圖模型,也就是搜索當天的任務。因此,我將用戶控件中日曆的selectedDate屬性綁定到用戶控件的視圖模型的屬性,所以當值更改時,視圖模型可以搜索當天的任務。
另外我希望這個用戶控件通知主應用程序,日曆中的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。 – XAMlMAX
**偉大的問題。 + 1 ** 我一直想知道這樣的事情是否可能。即具有視圖模型的可重用控件。但我不認爲有可能實現它。 也許你可以在不同的地方爲可重複使用的控件設置不同的視圖模型,而不僅僅是針對特定的控件。 在正常情況下,我們在後面的代碼中編寫代碼,並使用**依賴屬性**和這些**屬性更改**時的操作來實現可重用性。 – ViVi
@ Vishakh369:對,在代碼隱藏和依賴屬性中很容易做到,我有幾個簡單的用戶控件,可以從主應用程序到用戶控件以及從用戶控件到主應用程序兩種方式通知更改。但是這個用戶控件沒有內部邏輯。但是當你需要一個內部邏輯時,如果你的主應用程序使用了很多這種控件,那麼最終你有一個主應用程序使用MVVM,但是如果用戶控件是應用程序的50%,會發生什麼?然後,我將只有MVVM的50%的應用程序。以及如何使我的用戶控制單元測試? –