UI object, on samelevel of XAML Tree, as CommandParameter - wpf

I have an XAML tree as follows:
<Window>
<Grid>
<DockPanel>
<DataGrid>
<DataGrid.Resources>
<CheckBox Command="{Binding Command}" CommandParameter="??" />
</DataGrid.Resources>
</DataGrid>
<StackPanel>
<ChartLegend>
</ChartLegend>
<DataChart>
</DataChart>
</stackPanel>
</DockPanel>
</Grid>
</Window>
I want to have DataChart object as CommandParameter on ViewModel from a Command on DataGrid.
My Findings:
I'm getting DockPanel object as CommandParameter, then I have to apply method FindName("") to get the DataChart. And do further modifications.
But I want the DataChart object directly, to avoid TypeCasting or searching down the Tree.

You can keep datachart as a named resource in your DockPanel resources and use static resource binding to command parameter. Then use ContentControl to host it.
like this...
<DockPanel>
<DockPanel.Resources>
<DataChart x:Key="MyDataChart">
</DataChart>
</DockPanel.Resources>
<DataGrid>
<DataGrid.Resources>
<CheckBox
Command="{Binding Command}"
CommandParameter="{StaticResource MyDataChart}" />
</DataGrid.Resources>
</DataGrid>
<StackPanel>
<ChartLegend>
</ChartLegend>
<ContentControl Content="{StaticResource MyDataChart}"/>
</stackPanel>
</DockPanel>
Hoping that you wont use same MyDataChart to host to another area (as that would result in "visual tree parent disconnect" error).
Although I must ask you this... why is there a lonely CheckBox in your DataGrid resources?
Also your's and mine solution breaks MVVM because we are supplying a UI control (Chart control) to a View Model.

Related

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

How to override the ItemsSource defined for an ItemTemplate?

I use an Item Template to define how the rows of my grid must be displayed. The grid definition (simplified) shows that the item template source is GridRows (a collection of rows) :
<grid ...>
(...)
<ScrollViewer
ItemTemplate="{StaticResource GridRowItemDataTemplate}"
ItemsSource="{Binding GridRows}" />
</ScrollViewer>
</grid>
So far, so good.
In the item template, the textbox is bound to ImportZoneName, which resolved of course to GridRows[i].ImportZoneName, and this is exactly what I want :
<DataTemplate x:Key="GridRowItemDataTemplate">
<Grid>
<TextBlock {Binding ImportZoneName}" />
<ComboBox
SelectedItem="{Binding SelectedModelingTypeValue}"
ItemsSource="{Binding ModelingTypes}" />
</Grid>
</DataTemplate>
Now the problem : I also want to bind the combo box to an other property (ModelingTypes) of my view model. This property is not linked in any way to GridRows. How can I tell WPF to override (or forget) the item template source?
Many, many thanks !
BTW, I did not find yet a simple guide for these simple binding cases... If anyone have a link to such a guide, I will bless him/her forever :)
You can get the DataContext of the parent list like this:
<DataTemplate x:Key="GridRowItemDataTemplate">
<Grid>
<TextBlock {Binding ImportZoneName}" />
<ComboBox
SelectedItem="{Binding SelectedModelingTypeValue}"
ItemsSource="{Binding DataContext.ModelingTypes,
RelativeSource={RelativeSource FindAncestor,
AncestorType=Grid}}" />
</Grid>
</DataTemplate>
Replace Grid with the type of the grid you are using (not sure which it is, not evident from the question), as long as its DataContext has the property ModelingTypes

Binding to two different DataContexts in a ContextMenu

I'm trying to bind to a property of a container from inside a DataTemplate. A simplified version of my markup looks like:
<Grid>
<Grid.Resources>
<DataTemplate DataType="{x:Type myCustomItem}">
<!--Visual stuff-->
<StackPanel>
<StackPanel.ContextMenu>
<ContextMenu>
<MenuItem Header="Add Item"
Command="{Binding myCustomItemsICommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type CustomContainerType}}, Path=ContainerProperty}"/>
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<CustomContainerType/>
</Grid>
My approach is based on this post but it doesn't seem to be working. The issue seems to arise from the placement of the ContextMenu within the visual tree. Basically I am trying to bind the Command to the DataContext of the DataTemplate but bind the CommandParameter to a DataContext outside the DataTemplate.
ContextMenus are not in the same visual tree as the rest of the controls, there are a few questions regarding how to do bindings accross that boundary but this might be somewhat difficult without specifying names.
ElementName fails as well because of the lacking tree connection, but you could use x:Reference in the Binding.Source instead.

WPF DataGridTemplateColumn databind to control outside?

Well i have a control thats placed in a usercontrol and it exposes a ICommand with a DepenceyPropety(ShowCommand),
and then i have a datagrid (wpf toolkit) with a few columns and one of them has a delete button
<Custom:DataGrid Margin="0" ItemsSource="{Binding Todos}" AutoGenerateColumns="False">
<Custom:DataGrid.Columns>
<Custom:DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<Custom:DataGridTemplateColumn>
<Custom:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button CommandParameter="{Binding}"
Command="{Binding ElementName=ConfirmDeleteDialog, Path=ShowCommand}"
Content="Delete" />
</DataTemplate>
</Custom:DataGridTemplateColumn.CellTemplate>
</Custom:DataGridTemplateColumn>
</Custom:DataGrid.Columns>
</Custom:DataGrid>
<local:ConfirmationDialog x:Name="ConfirmDeleteDialog"
Title="Confirm delete!"
d:LayoutOverrides="Width"
Message="Are you sure that you want to delete the selected todo?"
YesCommand="{Binding DeleteCommand}" />
and as im in a DataTemplate it does not find the element, and i can't combine ElementName and Relative source so how would i define a binding that can access an element declared outside the datatemplate?
The command im trying to bind to is located on ConfirmationDialog.YesCommand..
Well i solved it this way i changed my dialog to a to inherit from ContentControl instead of Control and added a content holder in it..
after that i moved the dialog so it wrapped the whole control, after that i added a routed event that i can listen to and that worked like a charm.

Resources