I am wrapping a custom user control into a dll and would like other references to the control to be able to get or set the result value by binding to the "Result" property.
This user control displays and binds two lists and implements something like a list item selector, where the user takes values from the "source" list to the "selected" list, but of course there is some additional logic that is ignored here.
Here's the CustomControl.cs code:
public partial class CustomControl : UserControl
{
public IEnumerable<string> Result
{
get { return (IEnumerable<string>)GetValue(resultProperty); }
set { SetValue(resultProperty, value); }
}
public static readonly DependencyProperty resultProperty = DependencyProperty.Register("Result", typeof(IEnumerable<string>), typeof(CustomControl),
new FrameworkPropertyMetadata(null,FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, ResultChangedEvent));
private static void ResultChangedEvent(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uc = d as CustomControl;
if (uc == null) return;
uc.viewModel.InitResult((IEnumerable<string>)e.NewValue);
}
//There's another IEnumerable<string> DependencyObject here called 'Source'
//....
internal CustomControlViewModel viewModel;
public CustomControl()
{
InitializeComponent();
ContentGrid.DataContext = viewModel = new CustomControlViewModel();
}
}
CustomControlViewModel.cs:
The SelectedList should be return a final list as 'Result' data.
internal class CustomControlViewModel
{
//In fact, the type here should contain a boolean status of whether it is selected or not, not a string.
public ObservableCollection<string> SourceList { get; set; } = new ObservableCollection<string>();
public ObservableCollection<string> SelectedList { get; set; } = new ObservableCollection<string>();
public void InitResult(IEnumerable<string> items)
{
SelectedList.Clear();
foreach (var name in items.Distinct())
SelectedList.Add(name);
}
public void OtherEventHandleList(string text)
{
//select item from SourceList
//SelectedList.Add(text);
}
}
Code for externally referencing UserControl:
MainWindowViewModel.cs:
class MainWindowViewModel
{
public ObservableCollection<string> RawList { get; set; } = new ObservableCollection<string>()
{ "data1", "data2", "data3" , "data4" };
public ObservableCollection<string> ResultList { get; set; } = new ObservableCollection<string>()
{ "data1", "data2" };
public void GetResult()
{
MessageBox.Show(string.Join(",", ResultList));
}
}
MainWindow.xaml:
<Window xmlns:cc="clr-namespace:CustomControl;assembly=CustomControl">
<Grid>
<cc:CustomControl Source="{Binding RawList}" Result="{Binding ResultList}"/>
</Grid>
</Window>
But the 'Result' Binding of this UserControl is only valid when it is assigned a value, and I could never find a suitable way to make the Result return the SelectedList in CustomControlViewModel.
How do I get DependencyProperty to return the viewModel's properties?
Because you set the
DataContextinside the constructor explicitly, you break theDataContextfor external bindings that target yourUserControl. For this reason and for reasons of reusability you never set theDataContextof a custom control internally. A control must always ignore its currentDataContext.A control should not contain any data logic. It should not calculate data and return any results.
Controls are modules of the application view. The view has the only responsibility to display data and interact with the user e.g. to collect input or modify data. It does not generate data.
Any computations and data processing must takle place in the view model (if it is data presentation related) or in the model (if it is business logic realted).
A custom control must not care about its
DataContext. If your control needs external data you must introduce a dependency property that the client (the externalDataContexte.g., yourMainViewModel) can bind to.Aside from that, you don't have a view model for each control but for each data scope. A view model per control is automatically eliminated when you follow the common practice of designing a custom control - which is to not have your control to depend on a
DataContext.MVVM is an application architectural design pattern. View model describes an application component and not control components. The application view model is composed of many classes. It somehow became a convention to name those root classes (within the class hierarchy) with the
ViewModelsuffix. Those root classes usually describe a data scope of the view. Multiple controls usually share the sameDataContext.From an MVVM perspective, all the logic of the control is UI related and therefore part of the view. It does not belong to any view model. Instead move it to "code-behind" (C# code). Those classes are not view model classes.
If you design your control properly you don't even have to care about the type of the source collections. Very much like the
ListBoxcan display any data without knowing their type. It does this by not caring about the actual data items. It just loads the item containers, assigns the data item to theDataContextof the item container and delegates the layout to the client. The client then will define aDataTemplatethat tells the item containers how they are rendered.You can replicate this behavior very easily.
The following example shows how to modify your code to make it data and data context agnostic:
CustomControl.xaml.cs
CustomControl.xaml
MainViewModel.cs
It's important that your view models (or non-DependencyObject binding sources in general) implement
INotifyPropertyChanged- even when they don't change property values.Move the code from
CustomControlViewModelto the application view model (e.g. theMainViewModelclass). If it contains data logic it doesn't belong to the view. It then belongs to the view model or model.MainWindow.xaml
If you only want to display a list of items and require your custom control to have some special behavior, then the recommended approach would be to extend
ItemsControl(orListBox) instead ofUserControl:CustomControl.xaml.cs
MainWindow.xaml