New to WPF/MVVM. I have a data object of type "MyData". One of its properties is of type "MySubsetData".
I show a collection of "MyData" objects in a datagrid.
<DataGrid ItemsSource="{Binding Path=MyDataCollection}">
<!-- Each row of the datagrid contains an item of type "MyData" -->
<DataGrid.Columns .../>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<local:MySubsetDataUserControl/>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
The row details should show the content of "MySubsetData". The view of the row details is in a separate user control (here: "MySubsetDataUserControl").
At the moment I don't set a view model for "MySubsetDataUserControl", so it inherits the data context from the parent's datagrid row.
<UserControl>
<!-- Namespace stuff not shown for simplicity -->
<Grid DataContext="{Binding Path=MySubsetData}">
<!-- Show the MySubsetData properties here -->
<!-- e.g. a textbox -->
<TextBox Text="{Binding Path=TextData, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</UserControl>
Altough this is working I face several problems with this approach:
All business logic will be in the user control parent's view model, where it simply doesn't belong. Making the view model messier than it need to be. Not to mention that command bindings in the user controls xaml look very ugly as well. It just doesn't feel right.
As more row details could be visible at the same time, I can't bind the properties of "MySubsetData" to an observable property in the view model. I.e. if I change a property in code (e.g. TextData) the change will not be reflected in the view. My workaround is not to alter the property "TextData". Instead I change the content of the textbox Text property, which in turn will update the "TextData" property. And that feels very wrong!
So I would like to use another view model for my user control, but I don't know how to access my data then.
<UserControl.DataContext>
<local:UserControlViewModel/>
</UserControl.DataContext>
How do I access "MySubsetData" now?
Let assume you have a view model like this:
public class ViewModel
{
public IEnumerable<MyData> MyDataCollection{get; private set;}
}
Where
public class MyData
{
public MySubsetData MySubsetData { get; }
}
Your view containing the DataGrid would be
<UserControl.DataContext>
<local:ViewModel>
</UserControl.DataContext>
<DataGrid ItemsSource="{Binding Path=MyDataCollection}">
<DataGrid.Columns .../>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<!-- each row of the items control has an implicit DataContext of MyData -->
<!-- so bind the DataContext of the subset control to MySubsetData -->
<local:MySubsetDataUserControl DataContext={Binding MySubsetData}/>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
Now your subset control can look like
<UserControl>
<Grid>
<TextBox Text="{Binding Path=TextData, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</UserControl>
On re-reading the question, maybe all I've done is repeat the Xaml from the question in a slightly different way.
However the various classes should be implementing INotifyPropertyChanged, e.g.
public class MySubsetData : INotifyPropertyChanged
{
public string TextData
{
get{...}
set{...; OnPropertyChanged("TextData"); }
}
}
Then the TextBox bound to the TextData property will reflect changes made in code.
Related
I'm totally lost with dependancy objects and binding. I often get things working without understanding why and how, this question is about knowing what should be happening.
I have a tiny user control with the following XAML
<Grid>
<Image Source="{Binding Icon}"></Image>
<TextBlock Text="{Binding Title}"></TextBlock>
</Grid>
My code behind has the following
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon", typeof(Image), typeof(MenuItem));
public Image Icon
{
get { return (Image)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(String), typeof(MenuItem));
public string Title
{
get { return (string)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
My MainWindow is empty, other than a reference to this control and to the ResourceDictionary. In the MainWindow code behind, I set the DataContext in the constructor.
<Window x:Class="AppUi.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:AppUi.Control"
Title="">
//set up to Resource Dictionary - all binding and styling works fine :)
<loc:MenuItem Icon="{Binding MailIcon}" Title="{Binding MailTitle}"></loc:MenuItem>
In the ModelView for the MainWindow, I have the following 2 properties
private Image_mailIcon;
public Image MailIcon{
//inotifyproperty implementation
}
private string _mailTitle;
public string MailTitle{
//inotifyproperty implementation
}
My question is, in the UserControl, how do I do the binding? Since it's a user control within a MainWindow, and the MainWindow already has a datacontext, I think the UserControl will inherit the DataContext from the parent (From what I have read).
So, in my UserControl XAML, should I be binding to the MainWindow's Code Behind properties OR to the ViewModel properties?
In other words, should my UserControl be
<Grid>
<Image Source="{Binding MailIcon}"></Image>
<TextBlock Text="{Binding MailTitle}"></TextBlock>
</Grid>
OR
<Grid>
<Image Source="{Binding Icon}"></Image>
<TextBlock Text="{Binding Title}"></TextBlock>
</Grid>
Or, because I'm using a DataContext and the UserControl inherits, do I even need the Dependancy Properties at all?
You normally don't want to overwrite DataContext passed through visual tree so you can use either ElementName or RelativeSource binding inside UserControl to change binding context. The easiest way to achive this is give UserControl some name and use it ElementName binding
<UserControl ... x:Name="myUserControl">
<!-- ... -->
<Grid>
<Image Source="{Binding Icon, ElementName=myUserControl}"/>
<TextBlock Text="{Binding Title, ElementName=myUserControl}"/>
</Grid>
<!-- ... -->
</UserControl>
This way binding is DataContext independent. You can also create UserControl with assumption it will always work with only specific type of DataContext and then you just use Path from that view model type but then DataContext of that UserControl must always be of the view model it's designed for (mostly inherited through visual tree)
<UserControl ...>
<!-- ... -->
<Grid>
<Image Source="{Binding MailIcon}"/>
<TextBlock Text="{Binding MailTitle}"/>
</Grid>
<!-- ... -->
</UserControl>
I would also change type of Icon property from Image to ImageSource for example. You already have Image control inside your UserControl and you just want to bind its Source
in the UserControl, how do I do the binding? ... the UserControl will inherit the DataContext from the parent
That is correct, the UserControl will inherit the DataContext from the parent Window. Therefore you can data bind from the UserControl directly to the parent Window.DataContext. Please note that you would bind to whatever object has been set as the DataContext, regardless of whether that was the code behind or a separate view model class.
However, you don't have to data bind to the parent's DataContext object in this situation... you have other options. You could data bind to your own UserControl DependencyPropertys using a RelativeSource Binding like this:
<TextBlock Text="{Binding Title, RelativeSource={RelativeSource
AncestorType={x:Type YourPrefix:YourUserControl}}}" />
You could also name your UserControl and reference its properties like this:
<TextBlock Text="{Binding Title, ElementName=YourUserControlName}" />
While this example seems to be more concise, don't overlook the first example, as RelativeSource is a useful and powerful friend to have.
should I be binding to the MainWindow's Code Behind properties OR to the ViewModel properties?
That's your choice... what do you want or need to data bind to? you just need to know that a direct data binding will use the auto set DataContext value, so if you don't want to use that, then you can just specify a different data source for the Binding as shown above.
Finally, regarding the need to use DependencyPropertys... you only need to declare them if you are developing a UserControl that needs to provide data binding abilities.
I need to create a childwindow that should contain the controls according to the model passed to it. For Example, If a model contains 5 properties (it can contain any number of properties), 2 of type string, 1 datetime and 2 lists, it should create 2 textboxes with labels as property name, 1 Datepicker with lable as property name, and 2 comboboxes with label as property name. Basically, controls should be created dynamically according to properties along with the Label as name of the property. I am following MVVM. Any help will be appreciated. Thanks.
Get the list of PropertyInfos of your model and wrap them in ViewModels. Then use DataTemplates with implicit keys to generate your controls.
Step1: Get PropertyInfoViewModels
var vms = model.GetType().GetAllProperties.Select(p=> ViewModelFactory.Create(p));
Your factory should return a StringPropertyViewModel for string properties, etc.
abstract class PropertyViewModel<T> : INotifyPropertyChanged
{
public string Caption {get; set;}
public T Value {get; set;}
}
Step2: DataTemplates
<DataTemplate TargetType="{x:Type sys:StringPropertyViewModel}">
<StackPanel Orientation="Horizontal">
<Label Text="{Binding Caption}" />
<TextBox Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
Step3:
Ensure the DataTemplates are in the Resources section of your Window or can be resolved via a ResourceDictionary.
Step4:
In the window ViewModel, expose the generated PropertyViewModels
<Window...>
...
<ItemsControl ItemsSource="{Binding ModelPropertyViewModels}">
<ItemsControl.Resources>
<!-- This might be a good place to post your DataTemplates --->
<ItemsControl.Resources>
</ItemsControl>
...
</Window>
I am using a DataTemplateSelector to swap input method for user based on whether he wants to enter text or pick a date value. Which means the selector switches between a TextBox and a DatePicker. Each control must use explicit way to update binding source. To sum up the user could pick a date or he could enter a text and once he is done he may click on apply button to update sources. Though only apply button updates the souce and not on focus lost.
The owner control of the DataTemplateSelector is a custom ContentControl called InputControl which is futhermore part of a UserControl.
Here is a small piece of pseudocode just to visualize things better:
public class InputControl : ContentControl
{
//// this method shall be executed once user clicks on apply button
//// inside this method the source of binding shall be updated no matter what input method used chose
public void Update()
{
}
}
Xaml looks kinda like this:
<UserControl>
<UserControl.Resources>
<DataTemplate x:key="text">
<TextBox Text="{Binding Mode=TwoWay, Path=., UpdateSourceTrigger=Explicit}"/>
</DataTemplate>
<DataTemplate x:key="date">
<DatePicker DateValue="{Binding Mode=TwoWay, Path=., UpdateSourceTrigger=Explicit}"/>
</DataTemplate>
<MyDataTemplateSelector x:key="myDataTemplateSelector"
TextTemplate="{StaticResource text}"
DateTemplate="{StaticResource date}">
</MyDataTemplateSelector>
</UserControl.Resources>
<Inputcontrol Content="{Binding Path=., Mode=TwoWay}" ContentTemplateSelector="{StaticResource myDataTemplateSelector}" />
</UserControl>
The selector looks like this
Public class MyDataTemplateSelector : DataTemplateSelector
{
Public DataTemplate TextTemplate { get; set;}
Public DataTemplate DateTemplate { get; set;}
Public DataTDemplate Select(.....)
{
....
}
}
Now the problem is how do I update the binding source from InputControl no matter what control is selected inside the template? If you read the comments above the method InputControl.Update() you will understand better what I mean with user updating source no matter what template.
If its TextBox selected the user shall be able to just call InputControl.Update() and it will update textbox binding source. If its DatePicker the user shall be able to do the same which is only to call InputControl.Update(). The source will get updated and Inputcontrol.Update() is a central point to trigger updating process no matter what control.
To sum up the method Update() is pretty central and updates the binding source no matter if its TextBox or DatePicker.
How do I do that?
I am having one view where I need to display some Grid and TabControl. There is one column on a grid that should display something like a Note (Remark) property. Since this field can contain large amount of data, I am going to have one tab with TextBox control that should allow user to see/edit note, while grid column will show only a few first letters on the note.
I am going to post relevant parts only:
public classSomeViewModel : ViewModelBase
{
public SomeViewModel()
{
TabScreens = New List<ViewModelBase>();
TabScreens.Add(new AnotherViewModel1());
TabScreens.Add(new AnotherViewModel2());
}
List<ViewModelBase> TabScreens{get;set;}
}
SomeView xaml:
<DataTemplate DataType="{x:Type vm:AnotherViewModel1}">
<vw:AnotherView1 />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:AnotherViewModel2}">
<vw:AnotherView2 />
</DataTemplate>
AnotherView2:
<Grid>
<TextBox Text={Binding Note} />
</Grid>
AnotherViewModel2:
public class AnotherViewModel2
{
public string Note {get;set;}
}
}
So TabControl on View is bound to TabScreens. DataTemplates ensure that both AnotherView1 and AnotherView2 will be loaded when SomeView is loaded. Each row in grid contains different remark. What is the cleanest way to synchronize SomeViewModel Remark and AnotherViewModel2 Remark?
Combine the 2 view models into a master view model with a single Remark property. Don't introduce a need to synchronize if it's not necessary.
I have a XAML code that should load my UserControl inside the TabControl.
If I put this XAML code:
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding Path=Gui}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
</DataTemplate>
I have absolutly nothing appear in the windows (Gui property is inside the ViewModel class and return a UserControl).
But if I put his XAML code instead of the previous one:
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
</DataTemplate>
I have the ViewModel Object loading:
(source: clip2net.com)
Here is a piece of code of the TextBoxInputViewModel that has the property Gui that should be binded to be able to get the Visual (usercontrol):
private UserControl gui;
public UserControl Gui
{
get
{
if (this.gui == null)
{
this.gui = new SimpleTextBoxInputControl();//Xaml User Control
this.gui.DataContext = this;//Bind the Visual and ViewModel
}
return this.gui;
}
}
Any idea how that I can get the UserControl instead of this object reference text?
The problem is that ItemSource is a collection, where as you're binding it to a property that is just one value. The error in the Output window that you're seeing is likely related to this.
Instead of returning a UserControl directly from your View Model, it would be better to return another View Model that represents the contents of the tab, and use templates to display that content. If you need it to be more dynamic than choosing the template based on the Type of the View Model, look into setting TabControl.ContentTemplateSelector. This needs to be set to a class that derives from DataTemplateSelector. You can use this class to decide which template to load based on the object bound to that tab.
you should create a template for your viewmodel in your app.xaml file like this
<DataTemplate DataType="{x:Type simpleModel:TextBoxInputViewModel}">
<myView:TextBoxInputControl />
</DataTemplate>
where simpleModel is the namespace of TextBoxInputViewModel, and TextBoxInputControl is the user control you want to show and myView is the namespace of that user control.