i would like to make a textblock tooltip conditionally visible.
i have the tooltip defined as:
<TextBlock>
<TextBlock.ToolTip>
<Grid>...</Grid>
</TextBlock.ToolTip>
</TextBlock>
where would visibility property go in that definition? it doesn't seem to like any of my guesses.
there are some suggestions of just working with grid visibility. the problem with that approach is making the grid invisible will still show empty tooltip box.. which is why i am trying to control tooltip visibility.
Try this. It won't leave an empty frame.
<TextBlock Text="test">
<TextBlock.ToolTip>
<ToolTip Visibility="Visible">
Hello
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
<TextBlock Text="test">
<TextBlock.ToolTip>
<ToolTip Visibility="Hidden">
Hello
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
The TextBlock with its ToolTip:
<TextBlock Text="{Binding Path=TextBoxText}">
<TextBlock.ToolTip>
<ToolTip Visibility="{Binding Path=ToolTipText, Converter={StaticResource StringToVisibilityConverter}}">
<Grid><TextBlock Text="{Binding Path=ToolTipText}" /></Grid>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
The object to are binding to the TextBlock:
public class TextBoxBindingObject
{
public string TextBoxText{ get; set; }
public string ToolTipText{ get; set; }
}
The converter:
[ValueConversion(typeof(string), typeof(Visibility))]
public class StringToVisibilityConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(value is string)
{
var stringValue = value as string;
if(!String.IsNullOrEmpty(stringValue))
return Visibility.Visible;
}
return Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Here you go;
<TextBlock Text="Dummy text">
<TextBlock.ToolTip>
<ToolTip Visibility="Collapsed">
<TextBlock Text="Text tooltip"></TextBlock>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
I realize this is a year old, but you can accomplish this in the code-behind.
private void control_ToolTipOpening(object sender, ToolTipEventArgs e)
{
if (condition)
e.Handled = true;
}
If you wanted to set a property here, you could do that, and bind it to the visibility. Don't forget to implement the INotifyPropertyChanged interface on your window.
Something like:
private void control_ToolTipOpening(object sender, ToolTipEventArgs e)
{
if (condition)
{
showControl=true;
e.Handled = true;
}
}
public Visibility showControl
{
get
{
return _showControl;
}
set
{
_showControl = value;
NotifyPropertyChanged("showControl");
}
}
and then bind it to the visibility property as
Visibility = "{Binding showControl}"
I'm typing this mainly to help anyone that comes across this from this point forward. I'm guessing you're not still stuck on this a year later, OP. =)
How about creating custom style for Tooltip ? That way you can re-use the same functionality at several places with minimal code.
Add this to a resourcedictionary and include it where ever you want to over-ride default tooltip behavior -
<Style TargetType="ToolTip" x:Key="{x:Type ToolTip}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Content,
RelativeSource={RelativeSource Self},
Converter={local:ToolTipContentConverter}}"
Value="">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
Followed by the converter -
[ValueConversion(typeof(object), typeof(string))]
public class ToolTipContentConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value ?? string.Empty;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
Hope this helps..
Amit
You should set the visibility on the grid :
<Window.Resources>
<BooleanToVisibilityConverter x:Key="visibilityConverter" />
</Window.Resources>
...
<Grid Visibility="{Binding IsToolTipVisible, Converter={StaticResource visibilityConverter}}>
...
</Grid>
If you don'e want the tooltip to show empty frame. You should create a separate tooltip ControlTemplate with all your required grid and textblocks and assign it to the tooltip template. This could help you solve the empty frame problem.
A much simpler solution than the other answers:
<TextBlock ToolTipService.IsEnabled="{Binding MakeToolTipVisible}">
<TextBlock.ToolTip>
<Grid>...</Grid>
</TextBlock.ToolTip>
</TextBlock>
where MakeToolTipVisible is your property.
Example: I want to store information about the line and add to my canvas
Line line = new Line();
line.X1 = 100;
line.Y1 = 100;
line.X2 = 500;
line.Y2 = 100;
line.Stroke = Brushes.Red;
line.StrokeThickness = 1;
line.ToolTip = new ToolTip { Content = "This is a line",Visibility= Visibility.Collapsed };
canvas.Children.Add(line);
Related
In my application I would like you highlight an index based on the value. for example:
ArrayList itemsList = new ArrayList();
private void button_Click(object sender, RoutedEventArgs e)
{
itemsList.Add("Coffie");
itemsList.Add("Tea");
itemsList.Add("Orange Juice");
itemsList.Add("Milk");
itemsList.Add("Mango Shake");
itemsList.Add("Iced Tea");
itemsList.Add("Soda");
itemsList.Add("Water");
listBox.ItemsSource = itemsList;
ApplyDataBinding();
}
private void ApplyDataBinding()
{
listBox.ItemsSource = null;
listBox.ItemsSource = itemsList;
}
It does not matter where in the listbox "Orange Juice" is I would like to highlight it based on its value. If the Position change it should be still highlighted. (Not based on the selected index)
If you want to highlight an item based on it's value, then you need to define your own datatemplate for an item and use a converter to provide appropriate brush for the background. Something like that:
<Window.Resources>
<local:TextToBrushConverter x:Key="TextToBrushConverter" />
</Window.Resources>
<Grid>
<ListBox Name="listBox" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Background="{Binding ., Converter={StaticResource TextToBrushConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Converter
class TextToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((value as String) == "Orange Juice")
{
return Brushes.Orange;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I would recommend that you place the items into a class, think Object Oriented design and then work off of properties (as flags) to give different states.
Then by using Xaml styles to key off those different properties to achieve the affect you are looking for.
Say for example we have an Order class with these properties
public class Order
{
public string CustomerName { get; set; }
public int OrderId { get; set; }
public bool InProgress { get; set; }
}
When an order is marked as in progress (InProgress = true) we want to show red in our list box say for "Alpha" and "Omega" which are in progress:
ListBox Xaml
Here is the Xaml which binds to our data (how you bind is up to you) and shows how to work with Style(s), DataTemplate, and DataTrigger(s) to achieve that:
<ListBox ItemsSource="{StaticResource Orders}"
x:Name="lbOrders">
<ListBox.Resources>
<DataTemplate DataType="{x:Type model:Order}">
<TextBlock Text="{Binding Path=CustomerName}" />
</DataTemplate>
<Style TargetType="{x:Type ListBoxItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=InProgress}"
Value="True">
<Setter Property="Foreground"
Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
</ListBox>
Here is the data setup in the pages's resource Xaml to do that, but it could be created in code behind:
<Window.Resources>
<model:Orders x:Key="Orders">
<model:Order CustomerName="Alpha"
OrderId="997"
InProgress="True" />
<model:Order CustomerName="Beta"
OrderId="998"
InProgress="False" />
<model:Order CustomerName="Omega"
OrderId="999"
InProgress="True" />
<model:Order CustomerName="Zeta"
OrderId="1000"
InProgress="False" />
</model:Orders>
</Window.Resources>
This should give you enough to start on and create a full featured UI.
I have an wpf Expander with templated header. In this template, i have an TextBox, which use Binding with Converter to set MaxLines and MinLines, which depends on Expander.IsExpanded.
The idea is to let user see first line of text and show more when Expander is expanded (alternate solution would be to make that TextBox.Visiblitity = Collapsed when expanded and another TextBox.Visibility = Visible, but user will lost their cursor position, marked text and i dont know what else)
<UserControl>
<UserControl.Resources>
<DataTemplate x:Key="MyTemplate">
<Grid>
<Grid.Resources>
<Converters:ExpandedToLineRowsConverter ExpandedLines="5"
CollapsedLines="1"
x:Key="ExpandedToLines"/>
</Grid.Resources>
<TextBox MaxLines="{Binding IsExpanded,
Converter={StaticResource ExpandedToLines},
RelativeSource={RelativeSource FindAncestor,
AncestorLevel=1,
AncestorType={x:Type Expander}},
UpdateSourceTrigger=PropertyChanged}"
MinLines="{Binding IsExpanded,
Converter={StaticResource ExpandedToLines},
RelativeSource={RelativeSource FindAncestor,
AncestorLevel=1,
AncestorType={x:Type Expander}},
UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<Expander Header="{Binding}"
HeaderTemplate="{StaticResource MyTemplate}">
<!-- other wpf controls under expander, they do not affect the problem -->
</Expander>
</UserControl>
ExpandedToLineRowsConverter is very simple:
public class ExpandedToLineRowsConverter : IValueConverter
{
public int ExpandedLines { get; set; }
public int CollapsedLines { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (bool)value ? ExpandedLines : CollapsedLines;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return (int)value != CollapsedLines;
}
}
Problem is, it is working only when expanding, not when collapsing (textbox stay in multiline mode even when Expander.IsExpanded = false).
When i set breakpoint in converter, it is returning corect number of lines, but it looks like the TextBox just ignore them.
I have no idea what to do...
Edit: sample VS2012 project with problem
I have problem with binding visibility in listbox-item template with property in parent object. Here is a little snippet from custom xaml style template:
<!-- DATA BINDING ITEM TEMPLATE -->
<StackPanel Orientation="Vertical">
<TextBlock Height="19"
....
Text="{Binding InfoTop}"/>
<Rectangle Height="1"
....
Visibility="{Binding _linesVisibility[0], RelativeSource={RelativeSource AncestorType=my:PatientsList}}"/>
<TextBlock Height="19"
....
Text="{Binding InfoMiddle}"
Visibility="{Binding _linesVisibility[0], ElementName=patientsControl}"/>
<Rectangle Height="1"
....
Visibility="{Binding _linesVisibility[1]}"/>
<TextBlock Height="19"
....
Text="{Binding InfoBottom}"
Visibility="{Binding _linesVisibility[1]}"/>
</StackPanel>
I managed to bind Text value by assigning ItemsSource in code file but i can't bind Visibility. As you can see i tried some different ideas but none of them work.
I have public variable public Visibility[] _linesVisibility = new Visibility[2]; in my custom control. This control contains listbox with custom style as above. How to bind properly my _linesVisibility to listbox-item style ?
You can't bind directly to an array:
Visibility="{Binding _linesVisibility[1]}"
This will not work.
You need to bind to a property and your class needs to implement INotifyPropertyChanged:
private Visibility backingVariable;
public Visbilility PublicProperty
{
get { return backingVariable; }
set
{
backingVariable = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("PublicVariable"));
}
}
}
It doesn't have to be a property of type Visibility. It can be any type as long as you bind through a converter that returns Visibility:
public class BoolToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool visibility = (bool)value;
return visibility ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Visibility visibility = (Visibility)value;
return (visibility == Visibility.Visible);
}
}
Usage:
Visibility="{Binding SomeBoolean, Converter={StaticResource boolToVisibilityConverter}}"
where the converter is declared in XAML like this:
<UserControl.Resources>
<globalConverters:BoolToVisibilityConverter x:Key="boolToVisibilityConverter" />
</UserControl.Resources>
I've bound enums to radio buttons before, and I generally understand how it works. I used the alternate implementation from this question: How to bind RadioButtons to an enum?
Instead of enumerations, I'd like to generate a runtime-enumerated set of a custom type and present those as a set of radio buttons. I have gotten a view working against a runtime-enumerated set with a ListView, binding to the ItemsSource and SelectedItem properties, so my ViewModel is hooked up correctly. Now I am trying to switch from a ListView to a ItemsControl with radio buttons.
Here's as far as I've gotten:
<Window.Resources>
<vm:InstanceToBooleanConverter x:Key="InstanceToBooleanConverter" />
</Window.Resources>
<!-- ... -->
<ItemsControl ItemsSource="{Binding ItemSelections}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:ISomeType}">
<RadioButton Content="{Binding Name}"
IsChecked="{Binding Path=SelectedItem, Converter={StaticResource InstanceToBooleanConverter}, ConverterParameter={Binding}}"
Grid.Column="0" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
InstanceToBooleanConverter has the same implementation as EnumToBooleanConverter from that other question. This seems right, since it seems like it just invokes the Equals method:
public class InstanceToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.Equals(parameter);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value.Equals(true) ? parameter : Binding.DoNothing;
}
}
The problem I am getting now is that I can't figure out how to send a runtime value as the ConverterParameter. When I try (with the code above), I get this error:
A 'Binding' cannot be set on the 'ConverterParameter' property of type 'Binding'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.
Is there a way to bind to the item instance, and pass it to the IValueConverter?
It turns out that it is much simpler to abandon using ItemsControl and instead go with ListBox.
It may be more heavy-weight, but that's mostly because it is doing the heavy lifting for you. It is really easy to do a two-way binding between RadioButton.IsChecked and ListBoxItem.IsSelected. With the proper control template for the ListBoxItem, you can easily get rid of all the selection visual.
<ListBox ItemsSource="{Binding Properties}" SelectedItem="{Binding SelectedItem}">
<ListBox.ItemContainerStyle>
<!-- Style to get rid of the selection visual -->
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:SomeClass}">
<RadioButton Content="{Binding Name}" GroupName="Properties">
<!-- Binding IsChecked to IsSelected requires no support code -->
<RadioButton.IsChecked>
<Binding Path="IsSelected"
RelativeSource="{RelativeSource AncestorType=ListBoxItem}"
Mode="TwoWay" />
</RadioButton.IsChecked>
</RadioButton>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
As far as I know, there's no good way to do this with a MultiBinding, although you initially think there would be. Since you can't bind the ConverterParameter, your ConvertBack implementation doesn't have the information it needs.
What I have done is created a separate EnumModel class solely for the purpose of binding an enum to radio buttons. Use a converter on the ItemsSource property and then you're binding to an EnumModel. The EnumModel is just a forwarder object to make binding possible. It holds one possible value of the enum and a reference to the viewmodel so it can translate a property on the viewmodel to and from a boolean.
Here's an untested but generic version:
<ItemsControl ItemsSource="{Binding Converter={StaticResource theConverter} ConverterParameter="SomeEnumProperty"}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton IsChecked="{Binding IsChecked}">
<TextBlock Text="{Binding Name}" />
</RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The converter:
public class ToEnumModelsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var viewmodel = value;
var prop = viewmodel.GetType().GetProperty(parameter as string);
List<EnumModel> enumModels = new List<EnumModel>();
foreach(var enumValue in Enum.GetValues(prop.PropertyType))
{
var enumModel = new EnumModel(enumValue, viewmodel, prop);
enumModels.Add(enumModel);
}
return enumModels;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The EnumModel:
public class EnumModel : INPC
{
object enumValue;
INotifyPropertyChanged viewmodel;
PropertyInfo property;
public EnumModel(object enumValue, object viewmodel, PropertyInfo property)
{
this.enumValue = enumValue;
this.viewmodel = viewmodel as INotifyPropertyChanged;
this.property = property;
this.viewmodel.PropertyChanged += new PropertyChangedEventHandler(viewmodel_PropertyChanged);
}
void viewmodel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == property.Name)
{
OnPropertyChanged("IsChecked");
}
}
public bool IsChecked
{
get
{
return property.GetValue(viewmodel, null).Equals(enumValue);
}
set
{
if (value)
{
property.SetValue(viewmodel, enumValue, null);
}
}
}
}
For a code sample that I know works (but it's still quite unpolished - WIP!), you can see http://code.google.com/p/pdx/source/browse/trunk/PDX/PDX/Toolkit/EnumControl.xaml.cs. This only works within the context of my library, but it demonstrates setting the Name of the EnumModel based on the DescriptionAttribute, which might be useful to you.
You are so close. When you are need two bindings for one converter you need a MultiBinding and a IMultiValueConverter! The syntax is a little more verbose but no more difficult.
MultiBinding Class
IMultiValueConverter Interface
Edit:
Here's a little code to get you started.
The binding:
<RadioButton Content="{Binding Name}"
Grid.Column="0">
<RadioButton.IsChecked>
<MultiBinding Converter="{StaticResource EqualsConverter}">
<Binding Path="SelectedItem"/>
<Binding Path="Name"/>
</MultiBinding>
</RadioButton.IsChecked>
</RadioButton>
and the converter:
public class EqualsConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0].Equals(values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Second Edit:
The above approach is not useful to implement two-way binding using the technique linked in the question because the necessary information is not available when converting back.
The correct solution I believe is straight-up MVVM: code the view-model to match the needs of the view. The amount of code is quite small and obviates the need for any converters or funny bindings or tricks.
Here is the XAML;
<Grid>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton
GroupName="Value"
Content="{Binding Description}"
IsChecked="{Binding IsChecked, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
and code-behind to simulate the view-model:
DataContext = new CheckBoxValueCollection(new[] { "Foo", "Bar", "Baz" });
and some view-model infrastructure:
public class CheckBoxValue : INotifyPropertyChanged
{
private string description;
private bool isChecked;
public string Description
{
get { return description; }
set { description = value; OnPropertyChanged("Description"); }
}
public bool IsChecked
{
get { return isChecked; }
set { isChecked = value; OnPropertyChanged("IsChecked"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class CheckBoxValueCollection : ObservableCollection<CheckBoxValue>
{
public CheckBoxValueCollection(IEnumerable<string> values)
{
foreach (var value in values)
this.Add(new CheckBoxValue { Description = value });
this[0].IsChecked = true;
}
public string SelectedItem
{
get { return this.First(item => item.IsChecked).Description; }
}
}
Now that I know about x:Shared (thanks to your other question), I renounce my previous answer and say that a MultiBinding is the way to go after all.
The XAML:
<StackPanel>
<TextBlock Text="{Binding SelectedChoice}" />
<ItemsControl ItemsSource="{Binding Choices}">
<ItemsControl.Resources>
<local:MyConverter x:Key="myConverter" x:Shared="false" />
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton>
<RadioButton.IsChecked>
<MultiBinding Converter="{StaticResource myConverter}" >
<Binding Path="DataContext.SelectedChoice" RelativeSource="{RelativeSource AncestorType=UserControl}" />
<Binding Path="DataContext" RelativeSource="{RelativeSource Mode=Self}" />
</MultiBinding>
</RadioButton.IsChecked>
<TextBlock Text="{Binding}" />
</RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
The viewmodel:
class Viewmodel : INPC
{
public Viewmodel()
{
Choices = new List<string>() { "one", "two", "three" };
SelectedChoice = Choices[0];
}
public List<string> Choices { get; set; }
string selectedChoice;
public string SelectedChoice
{
get { return selectedChoice; }
set
{
if (selectedChoice != value)
{
selectedChoice = value;
OnPropertyChanged("SelectedChoice");
}
}
}
}
The converter:
public class MyConverter : IMultiValueConverter
{
object selectedValue;
object myValue;
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
selectedValue = values[0];
myValue = values[1];
return selectedValue == myValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
if ((bool)value)
{
return new object[] { myValue, Binding.DoNothing };
}
else
{
return new object[] { Binding.DoNothing, Binding.DoNothing };
}
}
}
By default menu items become disabled when its command cannot be executed (CanExecute = false). What is the easiest way to make the menu item visible/collapsed based on the CanExecute method?
Thanks for the solution. For those wanting explicit XAML this might help:
<Window.Resources>
<BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter" />
</Window.Resources>
<ContextMenu x:Key="innerResultsContextMenu">
<MenuItem Header="Open"
Command="{x:Static local:Commands.AccountOpened}"
CommandParameter="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
CommandTarget="{Binding Path=PlacementTarget, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
Visibility="{Binding Path=IsEnabled, RelativeSource={RelativeSource Self}, Mode=OneWay, Converter={StaticResource booleanToVisibilityConverter}}"
/>
</ContextMenu>
In my case, the context menu is a resource, so the binding for the visibility must use the RelativeSource Self binding setup.
As a side, for the CommandParameter, you might also pass the DataContext of the item whom was clicked to open the context menu. And in order to route the command bindings to the parent window, you will need to set the CommandTarget accordingly also.
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
CanExecute toggles the IsEnabled property so just watch this and keep everything in the UI. Create a separate style if you want to reuse this.
You can simply bind Visibility to IsEnabled (set to false on CanExecute == false).
You still would need an IValueConverter to convert the bool to visible/collapsed.
public class BooleanToCollapsedVisibilityConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//reverse conversion (false=>Visible, true=>collapsed) on any given parameter
bool input = (null == parameter) ? (bool)value : !((bool)value);
return (input) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Microsoft provides a BooleanToVisibilityConverter.
http://msdn.microsoft.com/en-us/library/system.windows.controls.booleantovisibilityconverter.aspx
I don't know if this is the easiest way, but you can always create a property which returns the CanExecute() and then bind the Visibility of your element to this property, using a IValueConverter to convert the boolean to Visibility.
Binding Visibility to IsEnabled does the trick, but the required XAML is unpleasantly long and complicated:
Visibility="{Binding Path=IsEnabled, RelativeSource={RelativeSource Self}, Mode=OneWay, Converter={StaticResource booleanToVisibilityConverter}}"
You can use an attached property to hide all the binding details and clearly convey your intent.
Here is the attached property:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace MyNamespace
{
public static class Bindings
{
public static bool GetVisibilityToEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(VisibilityToEnabledProperty);
}
public static void SetVisibilityToEnabled(DependencyObject obj, bool value)
{
obj.SetValue(VisibilityToEnabledProperty, value);
}
public static readonly DependencyProperty VisibilityToEnabledProperty =
DependencyProperty.RegisterAttached("VisibilityToEnabled", typeof(bool), typeof(Bindings), new PropertyMetadata(false, OnVisibilityToEnabledChanged));
private static void OnVisibilityToEnabledChanged(object sender, DependencyPropertyChangedEventArgs args)
{
if (sender is FrameworkElement element)
{
if ((bool)args.NewValue)
{
Binding b = new Binding
{
Source = element,
Path = new PropertyPath(nameof(FrameworkElement.IsEnabled)),
Converter = new BooleanToVisibilityConverter()
};
element.SetBinding(UIElement.VisibilityProperty, b);
}
else
{
BindingOperations.ClearBinding(element, UIElement.VisibilityProperty);
}
}
}
}
}
And here is how you would use it:
<Window x:Class="MyNamespace.SomeClass"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyNamespace">
<ContextMenu x:Key="bazContextMenu">
<MenuItem Header="Open"
Command="{x:Static local:FooCommand}"
local:Bindings.VisibilityToEnabled="True"/>
</ContextMenu>
</Window>