WPF Context Menu pops up after Binding changes - c#

I have a Border control inside a UserControl which is used as a switch for graphical changes. The Border has a context menu with two MenuItems: "Open" and "Close".
When the switch is closed only the "Open" MenuItem is visible, and when the switch is open only "Close" is visible. For some switches I need to completely disable Open or Close, so I don't want them to be visible at any time. Here is the code:
<Border.ContextMenu>
<ContextMenu Name="switchContextMenu">
<ContextMenu.Visibility>
<MultiBinding Converter="{StaticResource ContextMenuBoolToVisibility}">
<Binding Path="OpenAvailable" />
<Binding Path="OpenVisible" />
<Binding Path="CloseAvailable" />
<Binding Path="CloseVisible" />
</MultiBinding>
</ContextMenu.Visibility>
<MenuItem Name="miOpen" Header="{Binding Path=Resources.PowerControlSystem_OPEN, Source={StaticResource LocalizedStrings} }"
Click="miOpen_Click">
<MenuItem.Visibility>
<MultiBinding Converter="{StaticResource BooleanToVisibilityMultiValueAND}">
<Binding Path="OpenAvailable" />
<Binding Path="OpenVisible" />
</MultiBinding>
</MenuItem.Visibility>
</MenuItem>
<MenuItem Name="miClose" Header="{Binding Path=Resources.PowerControlSystem_CLOSE, Source={StaticResource LocalizedStrings} }"
Click="miClose_Click">
<MenuItem.Visibility>
<MultiBinding Converter="{StaticResource BooleanToVisibilityMultiValueAND}">
<Binding Path="CloseAvailable" />
<Binding Path="CloseVisible" />
</MultiBinding>
</MenuItem.Visibility>
</MenuItem>
</ContextMenu>
</Border.ContextMenu>
I use this switch as a generic component, so inside another .xaml code I set the "Open Available" and "Close Available" properties. Here is my problem:
I have a switch with the Open Available property set to False. By default, this switch is open. I can closed it, and that's OK. Then when I right click on this switch again the context menu is not visible. This is also OK.
The problem is when I change some values this switch opens again, and at that point of time, the Context Menu pops up. I think that the problem is in the Converter for the ContextMenu visibility. It is triggered at this point and the ContextMenu becomes visible.
Does anyone have an idea how to stop this behavior?

Instead of setting the ContextMenu visibility, you can style the Border
<Border>
<Border.Style>
<Style TargetType="Border">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu Name="switchContextMenu">
<MenuItem Name="miOpen" Header="{Binding Path=Resources.PowerControlSystem_OPEN, Source={StaticResource LocalizedStrings} }"
Click="miOpen_Click">
<MenuItem.Visibility>
<MultiBinding Converter="{StaticResource BooleanToVisibilityMultiValueAND}">
<Binding Path="OpenAvailable" />
<Binding Path="OpenVisible" />
</MultiBinding>
</MenuItem.Visibility>
</MenuItem>
<MenuItem Name="miClose" Header="{Binding Path=Resources.PowerControlSystem_CLOSE, Source={StaticResource LocalizedStrings} }"
Click="miClose_Click">
<MenuItem.Visibility>
<MultiBinding Converter="{StaticResource BooleanToVisibilityMultiValueAND}">
<Binding Path="CloseAvailable" />
<Binding Path="CloseVisible" />
</MultiBinding>
</MenuItem.Visibility>
</MenuItem>
</ContextMenu>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Value="False">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource ContextMenuBoolAggregate}">
<Binding Path="OpenAvailable" />
<Binding Path="OpenVisible" />
<Binding Path="CloseAvailable" />
<Binding Path="CloseVisible" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="ContextMenu" Value="{x:Null}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
So the trigger is conditionally removing the ContextMenu instead of changing its visibility. I used the ContextMenuBoolAggregate converter name to indicate you need to create a new converter that is determining the correct value to remove the context menu - I think it's clear how to implement that part.
If your condition is simple enough, you can use MultiDataTrigger instead of DataTrigger with MultiBinding and Converter:
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding OpenAvailable}" Value="False"/>
<Condition Binding="{Binding OpenVisible}" Value="False"/>
<Condition Binding="{Binding CloseAvailable}" Value="False"/>
<Condition Binding="{Binding CloseVisible}" Value="False"/>
</MultiDataTrigger.Conditions>
<Setter Property="ContextMenu" Value="{x:Null}"/>
</MultiDataTrigger>

I found solution in another way. I used ContextMenuService.IsEnabled property. When is set to false, Context Menu can't be shown. So, I added this in my Border.xaml code:
<ContextMenuService.IsEnabled>
<MultiBinding Converter="{StaticResource ContextMenuBoolToVisibility}">
<Binding Path="OpenAvailable" ElementName="mySwitch" />
<Binding Path="OpenVisible" ElementName="mySwitch" />
<Binding Path="CloseAvailable" ElementName="mySwitch" />
<Binding Path="CloseVisible" ElementName="mySwitch" />
</MultiBinding>
</ContextMenuService.IsEnabled>
I also changed my ContextMenuBoolToVisibility converter. Instead of returning Visibility.Visible or Visibility.Collapsed, now returns true or false for ContextMenuService.IsEnabled property.

Related

How to access a MultiBinding defined in Application Resources

I´d like to reuse a MultiBinding and tried to use this solution, but I can´t seem to reach IsEnabled from a Setter Property.
So I tried this approach, but no cigar :
App.xaml
<Application x:Class="Test.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:model="clr-namespace:Test.MODEL"
StartupUri="MainWindow.xaml">
<Application.Resources>
<view:BooleanConverter x:Key="BooleanConverter" />
<view:BooleanMultiConverter x:Key="BooleanMultiConverter" />
<MultiBinding x:Key="OnOffBinding" Converter="{StaticResource BooleanMultiConverter}" ConverterParameter="OR">
<Binding Path="model:CustomerIsDefined" Converter="{StaticResource BooleanConverter}" />
<Binding Path="model:CustomerIsConfirmed" Converter="{StaticResource BooleanConverter}" />
</MultiBinding>
</Application.Resources>
MainWindow.xaml > These two attempts will not compile :
<TextBox IsEnabled="{StaticResource OnOffBinding}"/>
<TextBox IsEnabled="{MultiBinding {StaticResource OnOffBinding}}" />
Any ideas?
EDIT: After accepting Funcs answer I guess this has allready been answered in the linked thread. Sorry..
Try wrapping it in Style
<Application.Resources>
<view:BooleanConverter x:Key="BooleanConverter" />
<view:BooleanMultiConverter x:Key="BooleanMultiConverter" />
<Style x:Key="ControlEnabler" TargetType="Control">
<Setter Property="IsEnabled">
<Setter.Value>
<MultiBinding x:Key="OnOffBinding" Converter="{StaticResource BooleanMultiConverter}" ConverterParameter="OR">
<Binding Path="model:CustomerIsDefined" Converter="{StaticResource BooleanConverter}" />
<Binding Path="model:CustomerIsConfirmed" Converter="{StaticResource BooleanConverter}" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
MainWindow.xaml
<TextBox Style="{StaticResource ControlEnabler}"/>
Note TargetType="Control" making it generic.

Generically replace Converter in MultiBinding node

I have some RadioButton’s that I want to change the Visibility of based on multiple properties in the ViewModel. Each RadioButton’s Visibility will vary based on the same list of properties. I have the following xaml:
<RadioButton Command="{Binding Path=SomeCommand}"
CommandParameter="SomeCommandParameter"
Content="RB 1">
<RadioButton.Visibility>
<MultiBinding Converter="{StaticResource Rb1Visibility}">
<Binding Path="Value1"
RelativeSource="{RelativeSource Self}" />
<Binding Path="Value2"
RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</RadioButton.Visibility>
</RadioButton>
I would like to change the Converter for each RadioButton option, but everything else would remain the same (all the Bindings). I don’t want to duplicate all the xaml code for this. I originally tried creating a style for the RadioButton, but I could not figure out how to pass the Converter to the Style resource:
<Style x:Key="RbVisibilityStyle"
TargetType="{x:Type RadioButton}">
<Setter Property="Visibility">
<Setter.Value>
<MultiBinding Converter="{???? Pass in converter ?????}">
<Binding Path="Value1"
RelativeSource="{RelativeSource Self}" />
<Binding Path="Value2"
RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
I could create a “Visibility” property for each RadioButton in the ViewModel, but that adds a lot of code to the ViewModel and addition PropertyChanged event handling.
Is there an easy way to generically bubble up the MultiBinding so I don’t duplicate the code throughout the entire xaml file? Is there a different approach I should be taking?
Update - Adding a more complex example
<RadioButton Command="{Binding Path=SomeCommand}"
CommandParameter="SomeCommandParameter"
Content="RB 1">
<RadioButton.Visibility>
<MultiBinding Converter="{StaticResource Rb1Visibility}">
<Binding Path="Value1"
RelativeSource="{RelativeSource Self}" />
<Binding Path="Value2"
RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</RadioButton.Visibility>
</RadioButton>
<RadioButton Command="{Binding Path=SomeCommand2}"
CommandParameter="SomeCommandParameter2"
Content="RB 2">
<RadioButton.Visibility>
<MultiBinding Converter="{StaticResource Rb2Visibility}">
<Binding Path="Value1"
RelativeSource="{RelativeSource Self}" />
<Binding Path="Value2"
RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</RadioButton.Visibility>
</RadioButton>
<RadioButton Command="{Binding Path=SomeCommand3}"
CommandParameter="SomeCommandParameter3"
Content="RB 3">
<RadioButton.Visibility>
<MultiBinding Converter="{StaticResource Rb3Visibility}">
<Binding Path="Value1"
RelativeSource="{RelativeSource Self}" />
<Binding Path="Value2"
RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</RadioButton.Visibility>
</RadioButton>
How do I reduce the MultiBinding redundancy here?
would like to change the Converter for each RadioButton option,
Create a new MultiValueConverter which takes an extra parameter (the radio button option) and then simply route the call to the appropriate converter based on that option.
One can put the option in the Tag property on the control's Xaml.
Example
Its not clear to me what that option you mention is, so in my example let us uniquely identify each Radio Button by its Tag property and use the routing converter to find the appropriate converter based off of the Tag supplied.
<RadioButton Tag="1"/><RadioButton Tag="2"/>
Then change the style to use the new converter with the new parameter:
<Style x:Key="RbVisibilityStyle"
TargetType="{x:Type RadioButton}">
<Setter Property="Visibility">
<Setter.Value>
<MultiBinding Converter="{StaticResource RouterViaTagVisibilityConverter">
<Binding Path="Value1"
RelativeSource="{RelativeSource Self}" />
<Binding Path="Value2"
RelativeSource="{RelativeSource Self}" />
<Binding Path="Tag"
RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>

WPF Databinding to Window

I want to link the DataBinding of a listbox item with a new window
A "Name" button should open a new window with the DataBinding of the listbox item.
The Binding is a .xml File:
<People>
<Person image="Test.jpg" company="" position="" website="" chat="" notes="">
<Name first_name="Max" second_name="" last_name="Mustermann" salutation="" />
<Email email1="" email2="" email3="" />
<Phone_Numbers business="" private="" mobile="" />
<Business_Adress street="" place="" state="" postalcode="" country="" />
<Private_Adress street="" place="" state="" postalcode="" country="" />
</Person>
</People>
And the new Window should be linked to the Name Element of the Person.
Though there will be more then one person, but the window should be linked to the right person.
This is the XmlDataProvider:
<XmlDataProvider x:Name="XmlData" Source="People.xml" XPath="/People/Person" />
And the binding of the listbox looks like this:
<ListBox
Grid.Row="0"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding UpdateSourceTrigger=Explicit}"
x:Name="PeopleListBox"
SelectionMode="Single">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}, {1} {2}">
<Binding XPath="Name/#last_name" />
<Binding XPath="Name/#first_name" />
<Binding XPath="Name/#second_name" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Unless DataContext is set to XmlData your ItemsSource binding won't work. You did not mention where you define XmlDataProvider because if in Resources then you need to specify x:Key instead of x:Name and then you would have to specify it as Binding.Source. You also did not say what is People.xml as it must be added to your solution as a resource
<Window ...>
<Window.Resources>
<XmlDataProvider x:Key="XmlData" XPath="/People/Person" Source="People.xml"/>
</Window.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource XmlData}}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0}, {1} {2}">
<Binding XPath="Name/#last_name" />
<Binding XPath="Name/#first_name" />
<Binding XPath="Name/#second_name" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
or you can load it manually by setting XmlDataProvider.Source manually in code

WPF CheckBox multiBinding

I need to bind a checkBox to TWO property and I think that i have to use multiBindings
so far i have this, but this doesn't work.
<CheckBox x:Name="FilterAll" Content="All">
<CheckBox.IsChecked>
<MultiBinding>
<Binding Path="SearchEngineCompassLogView.FilterSearch.IsFilterAllEnable"
Source="{StaticResource CompassLogView}">
</Binding>
<Binding Path="SearchEngineCompassLogView.FilterSearch.IsFilterVisible"
Source="{StaticResource CoreServiceLogView}">
</Binding>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
is this even possible with MultiBinding?
You could use MultiBinding. And as ethicallogics said, you must use a converter to do the actual logic of the parameters (whether you want to do AND, OR, whatever. You can see a little more about those here
I'm not sure what you are trying to affect on your checkbox, but in the end it will look something like this.
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource MultiBoolConverter}">
<Binding Path="SearchEngineCompassLogView.FilterSearch.IsFilterAllEnable" Source="{StaticResource CompassLogView}"/>
<Binding Path="SearchEngineCompassLogView.FilterSearch.IsFilterVisible"
Source="{StaticResource CoreServiceLogView}"/>
</MultiBinding>
</CheckBox.IsChecked>
There is also another way to do this as well, which I sometimes find useful. It's called DataTriggers. If you've done any work with Styles and Templates then you may have seen them before. Here is an example based on your question:
<CheckBox>
<CheckBox.Style>
<Style TargetType={x:Type CheckBox}>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path="SearchEngineCompassLogView.FilterSearch.IsFilterAllEnable" Source="{StaticResource CompassLogView}" Value="True"/>
<Condition Binding="{Binding Path="SearchEngineCompassLogView.FilterSearch.IsFilterVisible" Source="{StaticResource CoreServiceLogView}" Value="True"/>
</MultiDataTrigger.Conditions>
<Setter Property="CheckBox.IsChecked" Value="True"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
You must specify converter in MultiBinding.Multibinding

Value Converter not getting called when data changes

I'm working on a user control right now in which I have a path as part of the control. There are 3 possible paths I might want to display based on the values of certain data. To determine which Path I want to use, I have a value converter that takes in the data and returns a number to represent which of the paths I should use.
My first thought was to just use the property changed callback from the two dependency properties I am getting data from, but those callbacks must be static and the XAML code is always non-static.
My second attempt is to now use datatriggers with the value converter described above. Below is the code I have.
<Path x:Name="path" Stretch="Fill" Width="111.75" Height="118.718" Data="F1M205.917,103.0088C189.333,93.8108,170.128,88.9998,150,88.9998C129.873,88.9998,110.584,93.8108,94.167,102.8408L116.1,144.2508L150,208.7178L183.9,144.2508z" Canvas.Left="0" Canvas.Top="0">
<Path.Resources>
<Style TargetType="{x:Type Path}">
<Style.Triggers>
<DataTrigger Value="-1">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource ToleranceRangeTypeChecker}">
<Binding ElementName="UserControl" Path="ToleranceZoneLowerBound" />
<Binding ElementName="UserControl" Path="ToleranceZoneUpperBound" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Data" Value="F1M205.917,103.0088C189.333,93.8108,170.128,88.9998,150,88.9998C129.873,88.9998,110.584,93.8108,94.167,102.8408L116.1,144.2508L150,208.7178L183.9,144.2508z" />
<Setter Property="Width" Value="111.75" />
<Setter Property="Height" Value="118.718" />
<!--<Setter Property="Canvas.SetLeft"-->
</DataTrigger>
<DataTrigger Value="0">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource ToleranceRangeTypeChecker}">
<Binding ElementName="UserControl" Path="ToleranceZoneLowerBound" />
<Binding ElementName="UserControl" Path="ToleranceZoneUpperBound" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Data" Value="F1M150,88.9998C129.873,88.9998,110.584,93.8108,94.167,102.8408L150,208.7178C150,208.7178,150,114.157407529625,150,88.9998z" />
<Setter Property="Width" Value="55.917" />
<Setter Property="Height" Value="118.718" />
<!--<Setter Property="Canvas.SetLeft"-->
</DataTrigger>
<DataTrigger Value="1">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource ToleranceRangeTypeChecker}">
<Binding ElementName="UserControl" Path="ToleranceZoneLowerBound" />
<Binding ElementName="UserControl" Path="ToleranceZoneUpperBound" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Data" Value="F1M205.917,103.0088C189.333,93.8108 170.128,88.9998 150,88.9998 150,113.365662567029 150,208.7178 150,208.7178L183.9,144.2508z" />
<Setter Property="Width" Value="111.75" />
<Setter Property="Height" Value="118.718" />
<!--<Setter Property="Canvas.SetLeft"-->
</DataTrigger>
</Style.Triggers>
</Style>
Another thought I had was to instead actually have 3 different paths and use setters to change the visibility of each, but I think that having one path and changing it's propertise would be more logical. I would also prefer one path because my goal would be to eventually animate between the paths rather than have them change instantly.
Thanks!
You can get the instance by casting the sender parameter in the property change callbacks.

Categories