Example: I have a form that can perform some work.
While the work is done i need to hide some UI elements.
What I do now is set the converter for each element that I need to be hidden:
<Button1 .. Visibility="{Binding Executing, Converter={StaticResource BoolToVisibilityInversed}}"/>
<TextBlock2 .. Visibility="{Binding Executing, Converter={StaticResource BoolToVisibilityInversed}}"/>
<CancelButton .. Visibility="{Binding Executing, Converter={StaticResource BoolToVisibility}}"/>
What I would like to have is some kind of variable in xaml to assign once and be able to bind to:
(pseudo code. doesn't work)
<!-- The Variables -->
<Visibility Name="VisibleWhileExecuting" Value="{Binding Executing, Converter={StaticResource BoolToVisibility}}" />
<Visibility Name="HiddenWhileExecuting" Value="{Binding Executing, Converter={StaticResource BoolToVisibilityInversed}}" />
<!-- The Elements -->
<Button1 .. Visibility="{Binding HiddenWhileExecuting}"/>
<TextBlock2 .. Visibility="{Binding HiddenWhileExecuting}"/>
<CancelButton .. Visibility="{Binding VisibleWhileExecuting}"/>
Is there a way to do so?
Update:
What I have come up to is creating two icons displaying the work status. Then I can bind to that icon's visibility.
something like this (didn't check)
<Image Name="ExecutingIcon" .. Visibility="{Binding Executing, Converter={StaticResource BoolToVisibility}}"/>
<Image Name="IdleIcon" .. Visibility="{Binding Executing, Converter={StaticResource BoolToVisibilityInversed}}"/>
<Button1 .. Visibility="{Binding ElementName=IdleIcon, Path=Visibility}" />
<CancelButton .. Visibility="{Binding ElementName=ExecutingIcon, Path=Visibility}"/>
If you are manually setting the boolean values, then you can directly use
Visibility HiddenWhileExecuting;
Visibility VisibleWhileExecuting;
instead of boolean values, which you can bind directly and need not send it to converter.
You can handle the visibility in the C# code as follows
HiddenWhileExecuting = Visibility.Hidden;
Related
So I have a number of XAML pages with various Controls, most of them with a TextBlock indicating the intended content. Like:
<TextBlock x:Name="txbCustomerName"
Text="Customer Name"/>
<TextBox x:Name="txtCustomerName"
Text="{Binding DataObject.CustomerName}"/>
I'm in the process of replacing the TextBlocks with Labels, which would look like this:
<Label x:Name="lblCustomerName"
Content="Customer Name"
Target="{Binding ElementName=txtCustomerName}"/>
<TextBox x:Name="txtCustomerName"
Text="{Binding DataObject.CustomerName}"/>
So far, so good. However, there are Controls that aren't always visible. Accordingly, the associated TextBlock follows suit:
<TextBlock x:Name="txbInvoiceAddressStreet"
Text="Street Name"
Visibility="{Binding DataObject.DifferentInvoiceAddress, Converter={StaticResource BoolToVisibility}}"/>
<TextBox x:Name="txtInvoiceAddressStreet"
Text="{Binding DataObject.InvoiceAddressStreet}"
Visibility="{Binding DataObject.DifferentInvoiceAddress, Converter={StaticResource BoolToVisibility}}"/>
I more or less hoped that the Label's Visibility would be automagically equal to that of its Target by default, but apparently I'll have to work for it. Which is fine, it's my job after all.
This first draft works great:
<Label x:Name="txbInvoiceAddressStreet"
Content="Street Name"
Target="{Binding ElementName=txtInvoiceAddressStreet}"
Visibility="{Binding Path=Visibility, ElementName=txtInvoiceAddressStreet}"/>
<TextBox x:Name="txtInvoiceAddressStreet"
Text="{Binding DataObject.InvoiceAddressStreet}"
Visibility="{Binding DataObject.DifferentInvoiceAddress, Converter={StaticResource BoolToVisibility}}"/>
You'll note that the Binding for my Label's Visibility is linked to the same element as Target instead of targeting the same data element as the TextBlock. I feel it concentrates relevant information in the TextBox instead of spreading it on both controls.
All this is working fine. Still, I can't help feeling that I might take it a step further if I found a way to apply that Binding to the TextBox's property directly through the Label's Target property instead of reusing the TextBox's name.
Like this, except it doesn't work because Source isn't a dependency property:
Visibility="{Binding Path=Visibility, Source={Binding Path=Target, RelativeSource={RelativeSource Self}}}"
As I said, this does not work. However, I hope it conveys a sense of what I'm trying for.
The ultimate step after that, of course, would be to move Visibility to the Labels' default style, so if there's a way to do that I'd like to know about it.
include Target property in binding path:
<Label Visibility="{Binding Path=Target.Visibility, RelativeSource={RelativeSource Self}}"/>
and it can be a part of Style:
<Style TargetType="Label">
<Setter Property="Visibility"
Value="{Binding Path=Target.Visibility, RelativeSource={RelativeSource Self}}"/>
</Style>
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
I have 3 listboxes all bound two 3 separate observable collections of the same type. My ViewModel has the observable collections exposed via properties. This is for some drag and drop grouping, source list box can have items dragged onto two different lists. But I want to give the user the ability to right click on a listboxitem and set the item's properties. Things like Type, Name, etc. I am using a data template in the since I want all three boxes to be the same in functionality. This works well, and I can get the context menu to pop up when I click on individual items with no problem. My trouble is that I have one propery called FieldType. It is an enum that has 4 potential values. I can't, for the life of me, figure out how to bind the IsChecked property of the MenuItem to that property... functionally anyway. Here is what I have tried....
<DataTemplate x:Key="SFTemplateWithContextMenu">
<TextBlock Text="{Binding Path=FieldName}" ><!--Tag="{Binding DataContext, ElementName=Window}"-->
<TextBlock.ContextMenu>
<ContextMenu >
<ContextMenu.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverterc" />
</ContextMenu.Resources>
<MenuItem Header="Rename..." />
<MenuItem Header="Field Type">
<MenuItem.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
</MenuItem.Resources>
<MenuItem Header="String" IsCheckable="True" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=DataContext.FieldType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverterc}, ConverterParameter={x:Static Configurator:TypeDesc.String}, PresentationTraceSources.TraceLevel=High}"/>
<MenuItem Header="Date" IsCheckable="True" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=DataContext.FieldType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.Date}}"/>
<MenuItem Header="Barcode" IsCheckable="True" IsChecked="{Binding RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Path=DataContext.FieldType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.BarCode}}" />
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
In the code above you can see on the String, Date, and Barcode MenuItems what I was trying to do (gotta love code that is a work in process). My issue is the exposed property that it should call. I don't know how, in my ViewModel property, to get to the item in the observable collection that corresponds to the item clicked. I have a value converter EnumToBoolean that will sit on the binding to get the checked or not. The problem is the property that is setting/getting that particular item in the observable collection.
Any thoughts? Need more code? Need me to clarify anything? How close am I? By the way, the ViewModel code is written in VB 2010.
Thanks
Bryce
EDIT:
I have tried the following using Angel's suggestion...
<DataTemplate x:Key="SFTemplateWithContextMenu">
<TextBlock x:Name="Field" Text="{Binding Path=FieldName}" >
<TextBlock.ContextMenu PlacementTarget="{Binding ElementName=Field}">
<MenuItem Header="Rename..." />
<MenuItem Header="Field Type">
<MenuItem.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
</MenuItem.Resources>
<MenuItem Header="Date" IsCheckable="True" IsChecked="{Binding PlacementTarget.DataContext.FieldType, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.String}, PresentationTraceSources.TraceLevel=High}"/>
</MenuItem>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
But this gives me an error that says...Cannot set properties on property elements. Not sure if this is a TextBlox vs TextBox issue? You used TextBox in your example... my guess is that your code would do the same. So I then tried the following...
<DataTemplate x:Key="SFTemplateWithContextMenu">
<TextBlock x:Name="Field" Text="{Binding Path=FieldName}" ><!--Tag="{Binding DataContext, ElementName=Window}"-->
<TextBlock.ContextMenu>
<ContextMenu PlacementTarget="{Binding ElementName=Field}" >
<MenuItem Header="Rename..." />
<MenuItem Header="Field Type">
<MenuItem.Resources>
<Configurator:EnumToBooleanConverter x:Key="EnumToBooleanConverter" />
</MenuItem.Resources>
<MenuItem Header="Date" IsCheckable="True" IsChecked="{Binding PlacementTarget.DataContext.FieldType, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static Configurator:TypeDesc.String}, PresentationTraceSources.TraceLevel=High}"/>
</MenuItem>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
But this causes binding errors...
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=Field'. BindingExpression:(no path); DataItem=null; target element is 'ContextMenu' (Name=''); target property is 'PlacementTarget' (type 'UIElement')
So it appears that the binding is not working. Any thoughts?
ContextMenu is not part of visual tree. So it wont, by default, connect \ bind to the datacontext of the TextBlock on which it is applied...
So 2 ways to do this...
Set ContextMenu.PlacementTarget and refer that as the Path in individual MenuItem's Binding.
e.g.
<TextBox x:Name="MyTextBlock">
<TextBox.ContextMenu PlacementTarget="{Binding ElementName=MyTextBlock}">
<MenuItem
Header="{Binding PlacementTarget.DataContext.MyHeader,
RelativeSource={RelativeSource
AncestorType={x:Type ContextMenu}}}"
</TextBox.ContextMenu>
</TextBox>
So in the example above... you want to connect menu item with the data context of the text box. So you define PlacementTarget on the ContextMenu. This placement target can only be set with 2 types of bindings... ElementName or StaticResource. And once the context menu is connected to the visual element via PlacementTarget, use the Path in the binding of the meuitem to resolve the data context property i.e. MyHeader.
OR
Use proxy element approach...
Bind datagrid column visibility MVVM
<DataTemplate x:Key="_ItemTemplateA">
<Grid Tag="{Binding Path=DataContext.Command, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentControl Content="{Binding}" ContentTemplate="{StaticResource ContentTemplateB}" Grid.Row="0" />
<ContentControl Name="uiContentPresenter" Content="{Binding ContentView}" Grid.Row="1" Height="0" />
<ContentControl DataContext="{Binding IsContentDisplayed}" DataContextChanged="IsDisplayed_Changed" Visibility="Collapsed" />
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Text"
Command="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
CommandParameter="{Binding}" />
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
The above data template is applied to an ItemsControl. The issue is that for the ContextMenu that is specified for the Grid, the PlacementTarget property is never actually getting set to anything so I cannot get to the Tag property of the Grid which is necessary for passing the Command that should execute on the parent UserControl down to the context menu. I've based this approach off of similar examples such as this: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/0244fbb0-fd5f-4a03-bd7b-978d7cbe1be3/
I've not been able to identify any other good way to pass this command down. This is setup this way because we are using an MVVM approach so the command we have to execute lives in the View Model of the user control this template is applied in. I've tried explicitly setting the PlacementTarget in a few different ways but it still always shows up as not set.
I realise that this is old and answered, but it doesn't seem properly answered. I came across a similar post and left a full answer. You might like to take a look as you can get it working with just a few adjustments to your code.
First, name your view UserControl... I generally name all of mine This for simplicity. Then remembering that our view model is bound to the DataContext of the UserControl, we can bind to the view model using {Binding DataContext, ElementName=This}.
So now we can bind to the view model, we have to connect that with the ContextMenu.DataContext. I use the Tag property of the object with the ContextMenu (the PlacementTarget) as that connection, in this example, a Grid:
<DataTemplate x:Key="YourTemplate" DataType="{x:Type DataTypes:YourDataType}">
<Grid ContextMenu="{StaticResource Menu}" Tag="{Binding DataContext,
ElementName=This}">
...
</Grid>
</DataTemplate>
We can then access the view model properties and commands in the ContextMenu by binding the ContextMenu.DataContext property to the PlacementTarget.Tag property (of the Grid in our example):
<ContextMenu x:Key="Menu" DataContext="{Binding PlacementTarget.Tag, RelativeSource=
{RelativeSource Self}}">
<MenuItem Header="Delete" Command="{Binding DeleteFile}" CommandParameter=
"{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource
AncestorType=ContextMenu}}" CommandTarget="{Binding PlacementTarget,
RelativeSource={RelativeSource Self}}" />
</ContextMenu>
Note the binding on the MenuItem.CommandTarget property. Setting this ensures that the target element on which the specified command is raised is the PlacementTarget, or the Grid in this case.
Also note the CommandParameter binding. This binds to the DataContext of the PlacementTarget, or the Grid in this case. The DataContext of the Grid will be inherited from the DataTemplate and so your data item is now bound to the object parameter in your Command if you're using some implementation of the ICommand interface:
public bool CanExecuteDeleteFileCommand(object parameter)
{
return ((YourDataType)parameter).IsInvalid;
}
public void ExecuteDeleteFileCommand(object parameter)
{
Delete((YourDataType)parameter);
}
Or if you are using some kind of RelayCommand delegates directly in your view model:
public ICommand Remove
{
get
{
return new ActionCommand(execute => Delete((YourDataType)execute),
canExecute => return ((YourDataType)canExecute).IsInvalid);
}
}
We have the same problem, but it works randomly. A contextmenu inside the controltemplate in a style for a listbox. We have tried to move the contextmenu to different levels inside the template but the same error occurs.
We think it might be connected to the refreshing of our ICollectionView that is the itemssource of the ListBox.
It seems that when the view refreshes the relative source binding inside the contextmenu is being evaluated before the PlacementTarget is being set.
It feels like a bug in either collectionviewsource or the ContextMenu of WPF...
Here is a working standalone XAML-only example based on your test case: a ContextMenu that retrieves a Command from the DataContext of its PlacementTarget using a Tag. You can reintroduce portions of your code until it stops working to try to find where the problem is:
<Grid>
<Grid.Resources>
<PointCollection x:Key="sampleData">
<Point X="10" Y="20"/>
<Point X="30" Y="40"/>
</PointCollection>
<DataTemplate x:Key="_ItemTemplateA">
<Grid Tag="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DockPanel}}}">
<TextBlock Text="{Binding X}"/>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding}"/>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
</Grid.Resources>
<DockPanel DataContext="{x:Static ApplicationCommands.Open}">
<ListBox ItemTemplate="{StaticResource _ItemTemplateA}" ItemsSource="{StaticResource sampleData}"/>
</DockPanel>
</Grid>
In this post,
ContextMenu.PlacementTarget is filled up from ContextMenuService.PlacementTarget when you do right click with mouse on button.
It means ContextMenu.PlacementTarget is filled up when the menu is shown up. You can check that by snoop.
EDIT 1
This code works fine.
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" BasedOn="{StaticResource DataGridRowBaseStyle}">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=DataContext}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu Style="{StaticResource ContextMenuStyle}"
ItemContainerStyle="{StaticResource MenuItemStyle}">
<MenuItem Header="Enable" Command="{Binding Path=PlacementTarget.Tag.(viewModels:PrinterListPageViewModel.EnableCommand), RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
In my main window xaml I have two user controls and two RadioButtons. I want the RadioButtons to control the Visibility of the user controls.
xaml excerpt:
<WpfApp2:ViewTree/>
<WpfApp2:ViewTab/>
<RadioButton x:Name="radioButton_Tree" GroupName="View"
IsChecked="True"> Tree View </RadioButton>
<RadioButton x:Name="radioButton_Tab" GroupName="View"
IsChecked="False" >Tab View</RadioButton>
in the user controls, I have something like this:
Visibility="{Binding IsChecked,
Converter={StaticResource BooleanToVisibilityConverter},
ElementName=Window1.radioButton_Tree}" >
At run time I get this error:
Cannot find source for binding with reference 'ElementName=Window1.radioButton_Tab'
What am I overlooking?
The name Window1 is not in the context of user control.
Can you use the code below?
<WpfApp2:ViewTree Visibility="{Binding IsChecked,
Converter={StaticResource BooleanToVisibilityConverter},
ElementName=radioButton_Tree}" />
<WpfApp2:ViewTab Visibility="{Binding IsChecked,
Converter={StaticResource BooleanToVisibilityConverter},
ElementName=radioButton_Tab}" />
<RadioButton x:Name="radioButton_Tree" GroupName="View"
IsChecked="True"> Tree View </RadioButton>
<RadioButton x:Name="radioButton_Tab" GroupName="View"
IsChecked="False" >Tab View</RadioButton>