WPF how to expose binding source as a resource - wpf

I have a TabControl located in the ParentView. Inside that TabControl I have ChildView that contains a button with command bound to ParentViewModel's ICommand:
<Button Command="{Binding DataContext.MyCommand, RelativeSource={RelativeSource AncestorType={x:Type local:ParentView}}}"
CommandParameter="{Binding}">
Quite a few of these actually. Is there a way of creating UserControl resource from that RelativeSource and DataContext so I can declare my buttons more like that?:
<Button Command="{Binding MyCommand, Source={StaticResource MyParentsDataContext}}"
CommandParameter="{Binding}">
Edit
What I came up so far is this:
<UserControl.Resources>
<RelativeSource x:Key="Parent" AncestorType="{x:Type local:ParentView}" />
</UserControl.Resources>
<Button Command="{Binding DataContext.MyCommand, RelativeSource={StaticResource Parent}}"
CommandParameter="{Binding}">
Slightly dryer and more readable, but still not sure how to get rid of DataContext if possible at all.

You can put anything you like in application.current.resources.
It's rather like a sort of in memory dictionary with key of string and value of object.
You could therefore add an instance of a class in a resource dictionary merged in app.xaml:
<local:AnyViewModel x:Key="VM"/>
If you need no parameters for this then you may find you never need to change that.
If there's more to this parent viewmodel then you can replace a dummy version.
If you then either merge in an instance from another resource dictionary with the same key in a more local scope or set it in code:
Application.Current.Resources["VM"] = newInstance;
That then would replace what a binding would find in resources.
If you want to do that dynamically then there's a bit of a trick with the dynamicresource to bear in mind.
You'd need:
<Button DataContext="{DynamicResource VM}" Command="{Binding MyCommand}"
CommandParameter="{This is now a complication}">
If the VM doesn't need to be dynamic then you're OK with staticresource:
<Button Command="{Binding MyCommand, Source={StaticResource VM}}"
CommandParameter="{Binding}">
If that isn't convenient for some reason you could make the resource a viewmodel which implements inotifypropertychanged and the specific viewmodel you're interested a property of that:
<Button Command="{Binding ParentViewmodel.MyCommand, Source={StaticResource VM}}"
CommandParameter="{Binding}">
That would allow you to switch out ParentViewmodel on the command.

Related

Binding a button nested in DataTemplate

I need to bind a button's command inside a datatemplate like below:
<GridViewColumn.CellTemplate>
<DataTemplate>
<Button Content="-" Cursor="Hand" Width="50"
Background="Red" x:Name="removeButton"
Command="{Binding Remove}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
Unfortunately it does not work. How can I bind a command in a button insade a datatemplate?
I found that thread in the forum:
Bindings in nested WPF DataTemplates
but the method given by person, who answered this question, does not work as well. I think, that something has changed in WPF since this time, I would you grateful for your help.
If Remove is defined in the view model of the parent ListView, you could bind to it using a RelativeSource:
Command="{Binding DataContext.Remove,
RelativeSource={RelativeSource AncestorType=ListView}}"
You could also set the AncestorType to Window or UserControl depending on where the command property is defined and where the DataTemplate is applied.

ContentControl Name as CommandParameter in Interaction Triggers in WPF

I have several buttons on a Content Control, and i want to know when each of them is pressed and released. From my research i found that the only way to do this is using Interaction Triggers, but correct me if there is an easier way.
Since i have 10+ buttons, and assigning PreviewMouseLeftButtonDown PreviewMouseLeftButtonUp takes up to 8 lines per button, i defined a custom button that i can reuse like this in my UserControl.Resources:
<UserControl.Resources>
<Button x:Key="ButtonWTriggers" x:Shared="False">
<inter:Interaction.Triggers>
<inter:EventTrigger EventName="PreviewMouseLeftButtonDown">
<inter:InvokeCommandAction Command="{Binding ButtonDownCmd}" CommandParameter="{Binding Path=Name, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentControl}}}"/>
</inter:EventTrigger>
<inter:EventTrigger EventName="PreviewMouseLeftButtonUp">
<inter:InvokeCommandAction Command="{Binding ButtonUpCmd}" CommandParameter="{Binding Path=Name, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentControl}}}"/>
</inter:EventTrigger>
</inter:Interaction.Triggers>
</Button>
</UserControl.Resources>
<ContentControl Name="testbutton" Content="{StaticResource ButtonWTriggers}"/>
And then i'm using it with:
<ContentControl Name="testbutton" Content="{StaticResource ButtonWTriggers}"/>
Since the resource is used several times, and i want to know exactly which button was pressed, i was thinking of using the ContentControl Name=<NAME HERE> as parameter for the command. I tried getting that name with CommandParameter="{Binding Path=Name, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentControl}}}" but its not working. The actions for Press and Release are executed but the parameter is empty, probably because it's trying to get the name property in the button instead of the content control right?
Can someone help me get the Name of the ContentControl as Parameter for the Trigger Command?
One way to deal with your problem is to use the RelativeSource.AncestorLevel property to skip the Button itself:
<inter:InvokeCommandAction (...) CommantParameter="{Binding Name, RelativeSource={RelativeSource AncestorType=ContentControl, AncestorLevel=2}}" />
But I consider that highly unreliable since it'll stop working if for example the Button's template contains a ContentControl (relative source would then resolve to Button itself again).
Much more reliable solution in my opinion is to bind Button.Tag property to the name of its ancestor, and then bind the InvokeCommandAction.CommandParameter to that property instead:
<Button (...) Tag="{Binding Name, RelativeSource={RelativeSource AncestorType=ContentControl}}">
(...)
<inter:InvokeCommandAction (...) CommandParameter="{Binding Tag, RelativeSource={RelativeSource AncestorType=Button}}" />
(...)
</Button>
If however you already use Button.Tag property for something else, an intermediate solution would be to make use of the Button.Parent property:
<inter:InvokeCommandAction (...) CommantParameter="{Binding Parent.Name, RelativeSource={RelativeSource AncestorType=Button}}" />
Note though that despite these methods are equivalent in your particular case, they're not equivalent in general, so pick wisely.

WPF Set Visibility on DataTemplate UI Element from MVVM View Model

I have a control that is set up as a DataTemplate:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<BooleanToVisibilityConverter x:Key="BoolToVis" />
<DataTemplate x:Key="KEYBOARD_EN">
<StackPanel>
<Button Visibility="{Binding Path=RegisterButtonVisible}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
</StackPanel>
</DataTemplate>
In this DataTemplate there is a control on which I wish to set the Visibility from various view models:
<Button Visibility="{Binding Path=RegisterButtonVisible}" Style="{StaticResource ...} > Register </Button>
I do routed events with my control, so I tried to set up something similar, but no matter what I try, the RegisterButtonVisible property does not get picked up:
public partial class MainKeyboard : UserControl
{
public static DependencyProperty RegisterButtonVisibleProperty;
public Visibility RegisterButtonVisible
{
get { return (Visibility)GetValue(RegisterButtonVisibleProperty); }
set { SetValue(RegisterButtonVisibleProperty, value); }
}
static MainKeyboard()
{
RegisterButtonVisibleProperty = DependencyProperty.Register("RegisterButtonVisible", typeof (Visibility),
typeof (MainKeyboard));
}
}
In my ViewModel I do this:
public Visibility RegisterButtonVisible // get, set, raisepropchange, etc
My DataTemplate with the button in it is wrapped in a userControl:
<UserControl x:Class="Bleh.Assets.MainKeyboard"
x:Name="TheControl"
Unloaded="UserControl_Unloaded">
<Viewbox>
<Grid>
<ContentControl Name="ctrlContent" Button.Click="Grid_Click" />
</Grid>
</Viewbox>
and is used in my views like this:
<assets:MainKeyboard
RegisterButtonVisible="Collapsed"
Loaded="MainKeyboard_Loaded">
<b:Interaction.Triggers>
<b:EventTrigger EventName="Register">
<b:InvokeCommandAction Command="{Binding ConfirmEmailAddressCommand}"/>
</b:EventTrigger>
<b:EventTrigger EventName="Enter">
<b:InvokeCommandAction Command="{Binding EnterKeyCommand}"/>
</b:EventTrigger>
</b:Interaction.Triggers>
</assets:MainKeyboard>
Please note this attribute:
RegisterButtonVisible="Collapsed"
This is my dependency property. It shows up in intelliesense, so the CLR has registered it correctly, but it does NOT pick up the property assignment (Collapsed is ignored).
This makes me feel like it is very close, but I do remember someone telling me I can not do this, thus the EventTriggers (this is a common issue with datatemplates and MVVM apparently).
So one option is to use something in the Interaction namespace, like I do my event triggers ( I just need to fire a "Visibility" trigger on this button somehow, at least I figure).
What is the right ANY way to do this in MVVM?
Fixing your code
In order to make your existing code work, you need to tell need to tell WPF what object RegisterButtonVisible should be read from. If it's a user control, give the UserControl a name and then reference that element via ElementName, like so:
<UserControl ... lots of stuff here
x:Name="TheControl"
>
In your button binding:
<Button Visibility="{Binding ElementName=TheControl, Path=RegisterButtonVisible}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
Of course, if you can't do that because the button and the usercontrol are in different files, you can still use an ancestor binding:
<Button Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type assets:MainKeyboard}},
Path=RegisterButtonVisible}"
Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
which, for each button, will walk up to find the closest instance of assets:MainKeyboard and then bind to the RegisterButtonVisible property.
Using MVVM
If you want to achieve the same using MVVM (instead of on a control), you need to use a converter to convert a boolean to a visibility property, like so:
<Button Visibility="{Binding IsRegistrationAllowed, Converter={StaticResource BoolToVis}}" Style="{StaticResource RegisterKeyboardButtonStyle}">Register</Button>
Of course, that assumes that your DataContext is set up correctly and pointing at your ViewModel.

Event trigger is not working inside ItemControl

I have a Item control which fills by a list, and list is collection of two parameters 'Time' and 'Description'. For it, i am using a HyperLinkButton for time and a Label for description.
What i want is that, i want to create click event using EventTrigger of hyperLink button in Main viewModel. My code is:
<ItemsControl
x:Name="transcriptionTextControl"
ItemsSource="{Binding MyCollectionOfTranscription, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<HyperlinkButton Content="{Binding Time}">
<ToolTipService.ToolTip>
<ToolTip Content="Time"/>
</ToolTipService.ToolTip>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<i:InvokeCommandAction
Command="{Binding HyperLinkButtonCommand}"
CommandParameter="{Binding
ElementName=transcriptionTextControl }" />
</i:EventTrigger>
</i:Interaction.Triggers>
</HyperlinkButton>
<sdk:Label Content="{Binding Description}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
When i build project, it doesn't give error but ICommand for hyperLink, shows warning as 'Cannot resolve symbol HyperLinkButtonCommand', while this event trigger is working fine outside this .
Not getting, what is actual problem behind it, plz give your valuable suggestion...
First off,
<i:InvokeCommandAction
Command="{Binding HyperLinkButtonCommand}"
CommandParameter="{Binding
ElementName=transcriptionTextControl }" />
The Binding is trying to locate a property called HyperLinkButtonCommand on the instance of the type that is contained within MyCollectionOfTranscription (you don't need to bind to this two-way).
(Side note, sending an ItemsControl into your Command is not MVVM.)
The ItemsControl iterates through each element in this collection, creates a copy of the template defined in ItemsControl.ItemTemplate, and sets the BindingContext equal to this element (I assume its a Transcript). You can tell this from the warnings you get from the binding failing to find your HyperLinkButtonCommand if you crank up databinding debug settings.
Assuming
HyperLinkButtonCommand is a command defined in your ViewModel, and
The root of this xaml is a Window (could be a UserControl, but am assuming here)
Your ViewModel is the DataContext of the Window
you can change the binding to the following and it should work (or you should get a clue from it)
<i:InvokeCommandAction
Command="{Binding HyperLinkButtonCommand,
RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
CommandParameter="{Binding
ElementName=transcriptionTextControl }" />
I prefer just to give my root an x:Name of "root" and use "ElementName=root" in cases like this.

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.

Resources