Binding properties through DataTemplates and ContentControl - wpf

I liked this answer, and it almost fit me.
But, how can I achieve this if my DataTemplate is in a external ResourceDictionary?
I'm using Prism and I provide the DataTemplates (for generic CRUD views) by each module, by using files like this:
<ResourceDictionary ... some hidden ns here ... >
<DataTemplate DataType="{x:Type model:Operation}">
<vw:OperationView />
</DataTemplate>
<DataTemplate DataType="{x:Type model:Customer}">
<vw:CustomerView />
</DataTemplate>
</ResourceDictionary>
Then I use this answer to merge the ResourceDictionaries into the Shell app and I have a default CRUD view which has that code:
<ContentControl Content="{Binding MyGenericObject}" />
That ContentControl automatically pull the correct view. It's working fine, but I want to know bind the property of the objects in each view.
That's a sample of these views (OperationView.xaml):
<UserControl x:Class="TryERP2.Cadastro.View.OperationView"
... some hidden NS ... >
<StackPanel>
<Label Content="Id" />
<TextBox Text="{Binding ????WHAT????}" />
<Label Content="Description" />
<TextBox Text="{Binding ????WHAT????}" />
</StackPanel>
</UserControl>
How can I bind these properties?

Since the DataContext behind OperationView will be an object of type Operation, then you simply bind to whatever property on Operation you want
<!-- DataContext will be model:Operation per your DataTemplate -->
<UserControl x:Class="TryERP2.Cadastro.View.OperationView"
... some hidden NS ... >
<StackPanel>
<Label Content="Id" />
<TextBox Text="{Binding Id}" />
<Label Content="Description" />
<TextBox Text="{Binding Description}" />
</StackPanel>
</UserControl>

The DataContext in the UserControl is your model object, so you can directly bind to its properties like this:
Text="{Binding SomeProperty}"
(If only a path is specified the binding is relative to the DataContext, note that in the answer you linked the intention was to have a TwoWay binding on the DataContext itself which was a primitive string, this cannot be done using a simple binding like {Binding .}, a property path targeting an actual property needs to be specified)

Related

WPF binding to both view and viewmodel´s properties

This is originally a question about Teleriks TabbedWindow control, but its really a generic.
Question. In a ItemTemplate, how to I bind to both the view and properties of the viewmodel
Below, my datasource is a list of Views (ie UserControls). I want to have the View presented in the ContentControl and some properties of the viewmodel presented in the header.
<telerik:RadTabbedWindow x:Class="Porter.Application.Views.MainWindow"
...
ItemsSource="{Binding Tabs2}">
<telerik:RadTabbedWindow.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.TabHeader}" />
</DataTemplate>
</telerik:RadTabbedWindow.ItemTemplate>
<telerik:RadTabbedWindow.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}" />
</DataTemplate>
</telerik:RadTabbedWindow.ContentTemplate>
</telerik:RadTabbedWindow>
UPDATED RESULT AFTER ANSWER FROM mm8
<telerik:RadTabbedWindow
ItemsSource="{Binding Tabs2}" <!--list of ViewModels (lets say ViewModelBase.cs)-->
...>
<telerik:RadTabbedWindow.Resources>
<DataTemplate DataType="{x:Type acc:SearchAccountsViewModel}">
<acc:SearchAccountsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type hello:HelloWorldViewModel}">
<hello:HelloWorldView/>
</DataTemplate>
</telerik:RadTabbedWindow.Resources>
<telerik:RadTabbedWindow.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TabHeader}" />
</DataTemplate>
</telerik:RadTabbedWindow.ItemTemplate>
<telerik:RadTabbedWindow.ContentTemplate>
<DataTemplate >
<ContentControl Content="{Binding}" />
</DataTemplate>
</telerik:RadTabbedWindow.ContentTemplate>
</telerik:RadTabbedWindow>
The Tab2 property should return an IEnumerable<T> where the type T has some public properties that you bind to in the XAML markup.
It may for example have a TabHeader property that you bind the header of the tab to in the ItemTemplate like this:
<telerik:RadTabbedWindow x:Class="Porter.Application.Views.MainWindow"
...
ItemsSource="{Binding Tabs2}">
<telerik:RadTabbedWindow.ItemTemplate>
<DataTemplate>
<TextBlock Text = "{Binding TabHeader}" />
</ DataTemplate >
</ telerik:RadTabbedWindow.ItemTemplate>
</telerik:RadTabbedWindow>
The ContentTemplate should be resolved automatically provided that you have defined a DataTemplate for the type T in scope of the RadTabbedWindow, for example in your App.xaml. It's in this template that you add your UserControl:
<DataTemplate DataType="{x:Type local:YourClass}">
<local:UserControl1 />
</DataTemplate>
You should not create a UserControl in the view model and add it to Tabs2. This breaks what the MVVM pattern is all about, i.e. separation of concerns. A view model doesn't create views.
If you don't have/want an implicit DataTemplate in App.xaml, you may of course also define the ContentTemplate inline:
<telerik:RadTabbedWindow x:Class="Porter.Application.Views.MainWindow"
...
ItemsSource="{Binding Tabs2}">
<telerik:RadTabbedWindow.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TabHeader}" />
</DataTemplate>
</telerik:RadTabbedWindow.ItemTemplate>
<telerik:RadTabbedWindow.ContentTemplate>
<DataTemplate>
<local:UserControl1 />
</DataTemplate>
</telerik:RadTabbedWindow.ContentTemplate>
</telerik:RadTabbedWindow>
The key point is that you bind to properties of T in both templates and that T is a POCO and not a control of some kind.

DataTemplate Binding depending on property type and with working property Binding

I check those articles about doing DataTemplate :
WPF DataTemplate Binding
WPF DataTemplate and Binding
WPF DataTemplate Textblock binding
and thoses about DataTemplate depending on property type :
WPF DataTemplate Binding depending on the type of a property
Dynamically display a control depending on bound property using WPF
I'm trying to display a property with different controls depending of the property value. I have this Xaml that is partialy working. I have 2 problems :
The property is displaying with the right control, but when I set the value it doesn't go back to the property. Means the "set" of My property is not call (but was before I creates the DataTemplate). I detect that the problem about setting the property is about the ="{Binding Path=.}" but I cannot find the solution to set it otherwise.
Also To be able to make it work, I had to "isolate" the Value into a single ViewModel so that the DataTemplate doesn't affect all the other control.
Can you help me find betters solutions to resolves those 2 problems?
Here is the xaml code of my View linked with MyContainerViewModel that has a "ChangingDataType" :
<UserControl >
<UserControl.Resources>
<!-- DataTemplate for strings -->
<DataTemplate DataType="{x:Type sys:String}">
<TextBox Text="{Binding Path=.}" HorizontalAlignment="Stretch"/>
</DataTemplate>
<!-- DataTemplate for bool -->
<DataTemplate DataType="{x:Type sys:Boolean}">
<CheckBox IsChecked="{Binding Path=.}" />
</DataTemplate>
<!-- DataTemplate for Int32 -->
<DataTemplate DataType="{x:Type sys:Int32}">
<dxe:TextEdit Text="{Binding Path=.}" MinWidth="50" Mask="d" MaskType="Numeric" HorizontalAlignment="Stretch"/>
<!--<Slider Maximum="100" Minimum="0" Value="{Binding Path=.}" Width="100" />-->
</DataTemplate>
<!-- DataTemplate for decimals -->
<DataTemplate DataType="{x:Type sys:Decimal}">
<!-- <TextBox Text="{Binding Path=.}" MinWidth="50" HorizontalAlignment="Stretch" />-->
<dxe:TextEdit Text="{Binding Path=.}" MinWidth="50" Mask="f" MaskType="Numeric" HorizontalAlignment="Stretch" />
</DataTemplate>
<!-- DataTemplate for DateTimes -->
<DataTemplate DataType="{x:Type sys:DateTime}">
<DataTemplate.Resources>
<DataTemplate DataType="{x:Type sys:String}">
<TextBlock Text="{Binding Path=.}"/>
</DataTemplate>
</DataTemplate.Resources>
<DatePicker SelectedDate="{Binding Path=.}" HorizontalAlignment="Stretch"/>
</DataTemplate>
</UserControl.Resources>
<ContentPresenter Content="{Binding MyChangingPropery}"/>
</UserControl>
More informations about 2 :
I wanted to have in a view a label and a property that changes depending of the object. Something like this :
<UserControl>
<UserControl.Resources>
<!-- ...DataTemplate here... -->
</UserControl.Resources>
<StackPanel>
<Label Content="Allo"/>
<ContentPresenter Content="{Binding MyChangingPropery}"/>
</StackPanel>
</UserControl>
But if I put the DataTemplate on this UserControl resources, it will also affect the Label "allo". So I had to create another view that contains the DataTemplate and MyChangingProperty so that the label Allo would not be affected. But the extra View created just for one property is kind of ugly to me, I'm sure there is a better way to isolate the DataTemplate so it can apply only to one UIControl.
<UserControl >
<StackPanel>
<Label Content="Allo"/>
<ContentPresenter Content="{Binding MyContainerViewModel}"/>
</StackPanel>
</UserControl>
Note : MyContainerViewModel here is linked with the first view described.
Thanks in advance!
One possible solution would be to use a DataTemplateSelector. You cannot bind primitive types using two way bindings because that would have to be somehow by reference via the DataTemplate which I think is not supported by WPF.
The DataTemplateSelector now selects the right DataTemplate based on the property type and searches for the right DataTemplate in the resources by name. This also solves your problem that your DataTemplates interacted with the Label.
So first you need to define a DataTemplateSelector that changes the DataTemplate based on the type of the property:
public class MyDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var fe = (FrameworkElement)container;
var prop = (item as MyViewModelType)?.MyChangingProperty;
if (prop is string)
return fe.FindResource("MyStringDT") as DataTemplate;
else if (prop is bool)
return fe.FindResource("MyBoolDT") as DataTemplate;
// More types...
return base.SelectTemplate(item, container);
}
}
Then you need to change the UserControl like this:
<UserControl>
<UserControl.Resources>
<local:MyDataTemplateSelector x:Key="MyDTSelector" />
<!-- DataTemplate for strings -->
<DataTemplate x:Key="MyStringDT">
<TextBox Text="{Binding MyChangingProperty, Mode=TwoWay}"
HorizontalAlignment="Stretch"/>
</DataTemplate>
<!-- DataTemplate for bool -->
<DataTemplate x:Key="MyBoolDT">
<CheckBox IsChecked="{Binding MyChangingProperty, Mode=TwoWay}" />
<!-- More DataTemplates... -->
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<Label Content="Allo"/>
<ContentPresenter Content="{Binding MyContainerViewModel}"
ContentTemplateSelector="{StaticResource MyDTSelector}" />
</StackPanel>
</UserControl>
You can find a bit more information regarding the DataTemplateSelector here.
You can of course also set a DataType on this new DataTemplates but it isn't required because the x:Key makes them unique anyway. But if you want then it has to look like this:
<DataTemplate x:Key="MyStringDT" DataType="{x:Type local:MyViewModelType}">
In my opinion, the previously posted answer is overkill. While a DateTemplateSelector is a useful thing to know about, it seems unnecessary to me in this scenario.
But if I put the DataTemplate on this UserControl resources, it will also affect the Label "allo".
The reason it affects the Label object is that the Label object is a ContentControl, and so does the same template-matching behavior for content types as your own ContentPresenter element does. And you've set the content of the Label object to a string value. But you can put anything you want as the content for it.
The way to fix the undesired effect is to intercept that behavior by changing the content from a string object to an explicit TextBlock (the control in the template that a string object normally gets assigned). For example:
<UserControl>
<UserControl.Resources>
<!-- ...DataTemplate here... -->
</UserControl.Resources>
<StackPanel>
<Label>
<TextBlock Text="Allo"/>
</Label>
<ContentPresenter Content="{Binding MyChangingPropery}"/>
</StackPanel>
</UserControl>
In that way, you bypass the template-finding behavior (since TextBlock doesn't map to any template and can be used directly), and the content for the Label will just be the TextBlock with the text you want.
This seems like a lot simpler way to fix the issue, than either to create a whole new view or to add a DataTemplateSelector.

How to get my original ViewModel?

I created a User Control that has ViewModelA() as its ViewModel then inside my View, there's a StackPanel that uses ViewModelA.Data as DataContext.
My problem is inside this StackPanel, I have a button that needs to implement my created ICommand inside ViewModelA(). How can I do that?
Is there anything like <Button DataContext="DataContext.Parent" /> or something like that?
Here's how I implemented my ViewModel and View:
App.xaml
<DataTemplate DataType="{x:Type vm:ViewModelA}">
<vw:ViewA />
</DataTemplate>
ViewA.xaml (where the button inside the stack panel should implement the ICommand)
<StackPanel x:Name="RightPaneDetails"
Grid.Column="1"
Margin="15,0,0,30"
DataContext="{Binding Data}">
<!-- Some controls goes here that binds to ViewModelA.Data properties -->
<StackPanel Orientation="Horizontal">
<Button DataContext={Binding} Command="{Binding LookupCommand}" /> <!-- This button should implement the ViewModelA.LookupCommand -->
</StackPanel>
</StackPanel>
TIA
PS, ViewModelA.Data is my model.
It looks like you have set DataContext as ViewModelA in your UC. So, you can use
<Button DataContext={Binding} Command="{Binding DataContext.LookupCommand, RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}}" />
It can also be written as :
<Button DataContext={Binding DataContext, RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor} } Command="{Binding LookupCommand}" />
Although #AnjumSKhan's solution is correct, I would like to propose alternative solution:
<StackPanel x:Name="RightPaneDetails"> <!-- do not set bind datacontext here -->
<!-- Some controls goes here that binds to ViewModelA.Data properties, e.g: -->
<TextBlock Text="{Binding Data.SomeProperty}" />
<!-- If there too many elements bound to Data, you can group them in stackpanel -->
<StackPanel Orientation="Horizontal">
<!-- This button is databound to ViewModelA.LookupCommand
DataContext doesn't have to be set, since it's inherited from parent. Actually entire stackpanel is redundant here-->
<Button Command="{Binding LookupCommand}" />
</StackPanel>
</StackPanel>
As you can see I just avoided setting DataContext on the parent RightPaneDetails so the children can easily access both ViewModelA.Data's properties (TextBlock) and ViewModelA's properties (Button)
Few other notes:
Notice, that instead of
RelativeSource={RelativeSource AncestorType=UserControl, Mode=FindAncestor}
you can write just
RelativeSource={RelativeSource AncestorType=UserControl}
Alternative solution to RelativeSource is ElementName:
<StackPanel x:Name="Root">
<StackPanel DataContext="{Binding Data}">
<Button Command="{Binding DataContext.LookupCommand, ElementName=Root}" />
I preffer this because existence of "Root" element is checked at compile time and it is more readable.
Avoid binding DataContext and another Property on the same element, e.g:
<Button DataContext="{Binding....}" Command="{Binding ...}" />
There is no guarantee, which binding will be evaluated first. You can, however, set IsAsyc=True to the second binding Command={Binding ..., IsAsync=True} to ensure it will be evaluated later than non async bindings

WPF: Set UserControl DataContext

I have the following MainWindow that lays out a left side navigation panel and a right side display area (both of these are UserControls).
Can someone explain how to assign the DataContext of the navigation panel (LinksView.xaml) to that of LinksViewModel.cs. I would like to bind a Command (BtnCompanyClickCommand) to the button and define BtnCompanyClickCommand in LinksViewModel.cs.
I have tried various methods that I found on StackOVerflow to set the DataContext but none of these solutions seem to work (binding RelativeSource, naming view and binding to name, etc.).
MainWindow.xaml
<StackPanel Orientation="Horizontal">
<vw:LinksView DataContext="{Binding RelativeSource={RelativeSource Self}}"/>
<ContentControl Content="{Binding CurrentUserControl}" />
</StackPanel>
LinksView.xaml
<StackPanel Orientation="Vertical">
<Button Content="Company" Width="75" Margin="3" Command="{Binding ElementName=Links,Path=BtnCompanyClickCommand}" />
</StackPanel>
FormsDictionary.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:SidekickAdmin.ViewModel"
xmlns:vw="clr-namespace:SidekickAdmin.View">
<DataTemplate DataType="{x:Type vm:CompanySummaryViewModel}">
<vw:CompanySummaryView>
<ContentControl Content="{Binding }" />
</vw:CompanySummaryView>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:LinksViewModel}">
<vw:LinksView />
</DataTemplate>
</ResourceDictionary>
EDIT
So I finally came across this explanation of how to set the DataContext of a UserControl which has to be done on the first child item of the UserControl.
Here is the modified LinksView.xaml that works.
<StackPanel Orientation="Vertical">
<StackPanel.DataContext>
<vm:LinksViewModel /> <!-- Bind the items in StackPanel to LinksViewModel -->
</StackPanel.DataContext>
<Button Content="Company" Width="75" Margin="3" Command="{Binding BtnCompanyClickCommand}" />
</StackPanel>
However, I am still not clear on why I have to set the DataContext of the child element and not the UserControl and why the DataTemplate for LinksView (set in FormsDictionary.xaml) doesn't tie into the DataContext of LinksViewModel. Any explanation would be appreciated.
First you have to refer to your DataContext (LinksViewModel.cs) in your XAML code.
You can do that either by directly instantiating it or use a ResourceDictionary. In the latter case you instantiate your DataConext either inside some .cs file or inside the ResourceDictionary .xaml file and store it in a named ResourceDictionary where you can find the reference later.
Second you simply have to associate the DataContext property of a View element like your LinksView.xaml with the corresponding DataContext.
This is pretty high-level and without any code but that's the basic idea behind it.
there should be an instance of LinksViewModel in MainWindowViewModel:
MainWindowViewModel.cs:
class MainWindowViewModel
{
public MainWindowViewModel()
{
LinksVM = new LinksViewModel();
}
public LinksViewModel LinksVM { get; private set; }
}
MainWindow.xaml
<StackPanel Orientation="Horizontal">
<vw:LinksView DataContext="{Binding LinksVM}"/>
<ContentControl Content="{Binding CurrentUserControl}" />
</StackPanel>
LinksView.xaml
<StackPanel Orientation="Vertical">
<Button Content="Company" Width="75" Margin="3" Command="{Binding BtnCompanyClickCommand}" />
</StackPanel>
since LinksView is explicitly created in MainWindow, there is no need in DataTemplate - it can be removed
<DataTemplate DataType="{x:Type vm:LinksViewModel}">
<vw:LinksView />
</DataTemplate>

How to properly bind a ListBoxItem in WPF?

I have a listbox and I want to iterate over a collection of Bars in my Foo-object.
<ListBox DataContext="{Binding Path=Foo.Bars}" >
<ListBox.Items>
<ListBoxItem>
<ContentControl DataContext="{Binding Path=.}" />
</ListBoxItem>
</ListBox.Items>
</ListBox>
This is the datatemplate I want to use.
<DataTemplate DataType="{x:Type Bar}">
<Label Content="hello stackoverflow" />
</DataTemplate>
If I snoop (--> examine by using the tool Snoop) my application, I notice that the entire collection of Bars is bound to the ContentControl, in stead of just 1.
How can I properly bind so the iteration over the collection goes fine?
You can just set the DataTemplate, and WPF does all the work. Set the ItemsSource to a list of Bar items, and then define a DataTemplate for Bar items.
<ListBox ItemsSource="{Binding Path=Foo.Bars}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type Bar}">
<Label Content="hello stackoverflow" />
</DataTemplate>
</ListBox.Resources>
</ListBox>
You could also set the ItemsTemplate directly by using <ListBox.ItemTemplate> instead of <ListBox.Resources>
See Data Binding Overview at MSDN.
First add your namespace to the Window element (Intellisense) :
xmlns:local="clr-namespace:yourenamespace"
Then the following XAML ( in Window.Resources is a clean way to do it ) :
<Window.Resources>
<ObjectDataProvider x:Key="DataProvider" ObjectType="{x:Type local:Foo}"/>
<DataTemplate x:Key="Template" >
<TextBlock Text="{Binding Bar}"/>
</DataTemplate>
</Window.Resources>
Place the Listbox :
<ListBox DataContext="{Binding Source={StaticResource DataProvider}}" ItemsSource="{Binding Bars}" ItemTemplate="DynamicResource Template" />
But, it depends on your code-behind object, you have to set a constructor to initialise public properties within your object which are ObservableCollection<> preferably (There is some restriction rules with object instance in XAML).

Resources