I got 3 Controls in my XAML.
If the Visibility from one of them changes to Visible, the others Visibility should change to Hidden. So that only one of them can be Visible at a time.
My Xaml
<Control x:Name="Unselected">
<Control.Style>
<Style TargetType="Control">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Selection, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Selected, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</Control.Style>
</Control>
<Control x:Name="Selection" Visibility="Hidden">
<Control.Style>
<Style TargetType="Control">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Unselected, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Selected, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</Control.Style>
</Control>
<Control x:Name="Selected" Visibility="Hidden">
<Control.Style>
<Style TargetType="Control">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=Selection, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=Unselected, Path=Visibility}" Value="Visible">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</Control.Style>
</Control>
Use 3 RadioButton and modify their Template and use their IsChecked property. Only one of them would be checked at a time. Bind your Visibility property to IsChecked property. If IsChecked is true, Visibility = Visible else Visibility = Hidden .
You can use Binding and PropertyChanged. Setting the property will raise PropertyChanged and Triggers will execute.
XAML:
<DockPanel LastChildFill="false">
<TextBox x:Name="Unselected" DockPanel.Dock="Top" Text="Unselected">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding EnumOptions}" Value="Unselected">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selection">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selected">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="Selection" DockPanel.Dock="Top" Text="Selection">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding EnumOptions}" Value="Unselected">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selection">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selected">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<TextBox x:Name="Selected" DockPanel.Dock="Top" Text="Selected">
<TextBox.Style>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding EnumOptions}" Value="Unselected">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selection">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
<DataTrigger Binding="{Binding EnumOptions}" Value="Selected">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<Button Content="Unselected" DockPanel.Dock="Top" Click="Button_Click"></Button>
<Button Content="Selection" DockPanel.Dock="Top" Click="Button_Click_1"></Button>
<Button Content="Selected" DockPanel.Dock="Top" Click="Button_Click_2"></Button>
</DockPanel>
Code behind:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
private void Button_Click(object sender, RoutedEventArgs e)
{
EnumOptions = Options.Unselected;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
EnumOptions = Options.Selection;
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
EnumOptions = Options.Selected;
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private Options _enumOptions;
public Options EnumOptions
{
get { return _enumOptions; }
set
{
_enumOptions = value;
OnPropertyChanged("EnumOptions");
}
}
}
public enum Options
{
Unselected,
Selection,
Selected
}
Basically, you will set need to set property like this
EnumOptions = Options.Unselected;
instead of
Unselected.Visibility = true;
Related
I have 3 TextBlocks whose visibility property depends on a validation rule on 3 different elements (TextBoxes). How can I refactor this code so that the ElementName is abstracted away and I just have one style defined instead of 3? Is it possible to pass in ElementName as a parameter at the TextBlocks which this style is applied to?
<Style x:Key="textBlock_stockPriceWarning" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=stockPriceTextBox}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=stockPriceTextBox}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="textBlock_taxRateWarning" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=ordinaryTaxRateBox}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=ordinaryTaxRateBox}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="textBlock_capGainsTaxRateWarning" TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=capitalGainsTaxRateBox}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=capitalGainsTaxRateBox}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
**Update: Per Mark's advice below, I tried using the following ControlTemplate and attached property definition:
<ControlTemplate x:Key="LabelWarning" TargetType="Label">
<Border BorderBrush="AliceBlue"/>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=(Validation.HasError),
ElementName={Binding Path=(views:ElementNameHelper.ElementAlias), RelativeSource={RelativeSource Mode=TemplatedParent}}}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError),
ElementName={Binding Path=(views:ElementNameHelper.ElementAlias), RelativeSource={RelativeSource Mode=TemplatedParent}}}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
public static class ElementNameHelper
{
public static readonly DependencyProperty ElementAliasProperty =
DependencyProperty.RegisterAttached("ElementAlias",
typeof(string), typeof(ElementNameHelper));
public static string GetElementAlias(DependencyObject obj)
{
return obj.GetValue(ElementAliasProperty).ToString();
}
public static void SetElementAlias(DependencyObject target, string value)
{
target.SetValue(ElementAliasProperty, value);
}
}
And invoking it like this:
<Label Content="Stock price should be a positive number."
FontSize="12" HorizontalAlignment="Center"
VerticalAlignment="Center" Foreground="Red"
Margin="1"
views:ElementNameHelper.ElementAlias="stockPriceTextBox"
Template="{StaticResource LabelWarning}"/>
But then the XAML compiler tells me that: A 'Binding' cannot be set on the 'ElementName' property of type 'Binding'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject. Do I have this setup correctly and is it just the case that you simply can't use this approach on ElementName? Or am I missing something with the attached property definition?
Usual way to do this is with an attached property.
If you just want a cheap-n-easy solution then you can use Tag, which is described in the documentation as "an arbitrary object value that can be used to store custom information". An attached property is the better way to do it though.
I have a view model bound to my TabItem DataContext. And it shows all the UIContols contained in that dockpanel (Lets say dockpanel's name is DoockpanelWithdata).
What i want to do is, when i have the value of the Viewmodel object equals to null then i want to show other Dockpanel saying there is no data(Lets say DoockpanelWithOutData).
My try is this:
How to switch these dockpanels when VM==null and not null ?
<TabControl>
<TabItem Name="Tab1" Cursor="Hand">
<ListView Name="lZ" ItemsSource="{Binding UObj}" SelectedItem="{Binding SelectedItem ,Mode=TwoWay}" >//On selected item i bind the the tabitem below which show different dockpanels on GM=null and not null
//columns here
</ListView>
</TabItem>
<TabItem Name="TabGraph" DataContext="{Binding GM , UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Cursor="Hand">
<DockPanel Name="DoockpanelWithOutData">
<DockPanel.Style>
<Style TargetType="DockPanel">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding GM, UpdateSourceTrigger=PropertyChanged}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</DockPanel.Style>
<TextBlock>There IS NO DATA</TextBlock>
</DockPanel>
<DockPanel Name="DoockpanelWithdata">
<DockPanel.Style>
<Style TargetType="DockPanel">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding GraphVM, UpdateSourceTrigger=PropertyChanged}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</DockPanel.Style>
//Some more data
</DockPanel>
</TabItem>
</TabControl>
In View Model(UR1R2_VM.cs) i have :
private ObservableCollection<UModel> uObj; //binded to itemsource of another TabItem
public ObservableCollection<UModel> UObj
{
get { return uObj; }
set { uObj= value; OnPropertyChanged("UObj"); }
}
private UModel selectedItem; //Binded to listView selecteditem of another tabitem
public UModel SelectedItem //HERE INSTANCE IS CREATED
{
get { return selectedItem; }
set
{
selectedItem = value;
GM = selectedItem != null ? new GM(selectedItem.Nom) : null; //HERE INSTANCE IS CREATED
OnPropertyChanged("SelectedItem");
}
}
private GM gM;
public GM GM
{
get { return gM; }
set
{
gM = value;
OnPropertyChanged("GM");
}
}
Here you go..
Just make sure that your GraphVM property is calling RaisePropertyChanged whenever it's changed (i.e., gets set to null). This is important in order for the trigger to get set.
<DockPanel Name="DoockpanelWithOutData">
<DockPanel.Style>
<Style TargetType="DockPanel">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext, UpdateSourceTrigger=PropertyChanged}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</DockPanel.Style>
<TextBlock>There IS NO DATA</TextBlock>
</DockPanel>
<DockPanel Name="DoockpanelWithdata">
<DockPanel.Style>
<Style TargetType="DockPanel">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding DataContext, UpdateSourceTrigger=PropertyChanged}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</DockPanel.Style>
//Some more data
</DockPanel>
I am trying to change the fill of a rectangle based upon a value in my ViewModel but despite having tried all the suggestions I have found online, it still isn't working.
The IsMouseOver trigger works fine but the DataTrigger is ignored despite the fact there is always either a 4 or 5 in my ViewModel property.
Could somebody show me where I may be going wrong?
Thanks.
This is my style:
<Style x:Key="FavouriteRectangleStyle" TargetType="{x:Type Rectangle}">
<Setter Property="Opacity" Value="1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Rectangle.Opacity" Value="0.5" />
</Trigger>
<DataTrigger Binding="{Binding Path=Theme}" Value="4">
<Setter Property="Rectangle.Fill" Value="{DynamicResource content__star__hex646464__shadow}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=Theme}" Value="5">
<Setter Property="Rectangle.Fill" Value="{DynamicResource content__star__favorit__hexebebeb__shadow}"/>
</DataTrigger>
</Style.Triggers>
</Style>
EDIT: So it seems that my Binding was incorrect due to my incorrect assumption that the DataContext of the UserControl is where I should direct the binding.
The Rectangle is in the template for a ListBox and has "Items" as it's DataContext so by changing the "Binding" it now works.
Many thanks for all assistance though:
SOLUTION:
<Style x:Key="FavouriteRectangleStyle" TargetType="{x:Type Rectangle}">
<Setter Property="Opacity" Value="1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Rectangle.Opacity" Value="0.5" />
</Trigger>
<DataTrigger Binding="{Binding DataContext.Theme,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl} }" Value="4">
<Setter Property="Rectangle.Fill"
Value="{DynamicResource content__star__hex646464__shadow}"/>
</DataTrigger>
<DataTrigger Binding="{Binding DataContext.Theme,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl} }" Value="5">
<Setter Property="Rectangle.Fill"
Value="{DynamicResource content__star__favorit__hexebebeb__shadow}"/>
</DataTrigger>
</Style.Triggers>
</Style>
Change your TargetType="Rectangle"
Here is the code I used to test
<Style x:Key="FavouriteRectangleStyle" TargetType="Rectangle">
<Setter Property="Fill" Value="Blue"/>
<Setter Property="Opacity" Value="1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Rectangle.Opacity" Value="0.5" />
</Trigger>
<DataTrigger Binding="{Binding Theme}" Value="4">
<Setter Property="Fill" Value="Red"/>
</DataTrigger>
<DataTrigger Binding="{Binding Theme}" Value="5">
<Setter Property="Fill" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
And the implementation
<Rectangle Style="{StaticResource FavouriteRectangleStyle}"/>
Inside the constructor of your window or usercontrol what you haveused, you should set this.DataContext = this; Otherwise binding not properly worked for the corresponding window or usercontrol.
That probably because you miss to implement the INotifyPropertyChanged interface, update you Theme property to this:
private int _theme = 5;
public int Theme
{
get
{
return _theme;
}
set
{
if (_theme == value)
{
return;
}
_theme = value;
OnPropertyChanged();
}
}
and make sure your codebehind or your viewmodel is implementing the interface
public partial class MainWindow : Window,INotifyPropertyChanged
{
here a full working sample:
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<SolidColorBrush x:Key="content__star__hex646464__shadow" Color="Red"/>
<SolidColorBrush x:Key="content__star__favorit__hexebebeb__shadow" Color="Green"/>
<Style x:Key="FavouriteRectangleStyle" TargetType="{x:Type Rectangle}">
<Setter Property="Opacity" Value="1"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Rectangle.Opacity" Value="0.5" />
</Trigger>
<DataTrigger Binding="{Binding Path=Theme}" Value="4">
<Setter Property="Rectangle.Fill" Value="{DynamicResource content__star__hex646464__shadow}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=Theme}" Value="5">
<Setter Property="Rectangle.Fill" Value="{DynamicResource content__star__favorit__hexebebeb__shadow}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<Rectangle Style="{StaticResource FavouriteRectangleStyle}" VerticalAlignment="Center" HorizontalAlignment="Center" Width="200" Height="100"></Rectangle>
<Button Content="Change Theme" Click="ButtonBase_OnClick"></Button>
</StackPanel>
and the code behind :
public partial class MainWindow : Window,INotifyPropertyChanged
{
private int _theme = 5;
public int Theme
{
get
{
return _theme;
}
set
{
if (_theme == value)
{
return;
}
_theme = value;
OnPropertyChanged();
}
}
public MainWindow()
{
this.InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Theme = Theme == 5 ? 4 : 5;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Answer in EDIT part of original question. Thanks for all assistance
I have a toggle button that has two data triggers that are bound to the buttons IsChecked property. Within the Data triggers I am setting the buttons content and also its command property. The problem that I am having is that when you click the button the data trigger is changing the command value and then firing the command. Is it possible to have the command fire first then the data trigger? Here is the code.
<ToggleButton x:Name="commandToggleButton" Grid.Row="0" Height="30" HorizontalAlignment="Left" Margin="26,24,0,0" VerticalAlignment="Top" Width="75">
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=commandToggleButton, Path=IsChecked}" Value="False">
<Setter Property="Content" Value="Build" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Command" Value="{Binding Build}" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=commandToggleButton, Path=IsChecked}" Value="True">
<Setter Property="Content" Value="Cancel" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Command" Value="{Binding CancelBuild}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
What I would like to happen is that when the button is clicked the first time for the Build command to be fired, then if it is clicked the second time for the CancelBuild command to be fired.
Keep it simple:
<ToggleButton IsChecked="{Binding EnableBuild}" FontWeight="Bold">
<ToggleButton.Style>
<Style TargetType="ToggleButton">
<Style.Triggers>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Content" Value="Build" />
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content" Value="Cancel" />
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
ViewModel:
public Command Build {get;set;}
public Command Cancel {get;set;}
//...
private bool _enableBuild;
public bool EnableBuild
{
get { return _enableBuild; }
set
{
_enableBuild = value;
NotifyPropertyChange(() => EnableBuild);
if (value)
Build.Execute();
else
Cancel.Execute();
}
}
Suppose I want to show/hide elements based on Values of Properties from DataContext, how can I acheive it?
// In MainWindow.xaml.cs: DataContext of MainWindow.xaml
public int Mode { get; set; }
In XAML, I want to show hide elements based on the Mode. How can I make the below work? Or what is the appropriate way of implementing this?
<StackPanel>
<StackPanel.Triggers>
<Trigger Property="Mode" Value="1">
<Setter TargetName="txt1" Property="Visibility" Value="Visible" />
<Setter TargetName="txt2" Property="Visibility" Value="Collapsed" />
<Setter TargetName="txt3" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="Mode" Value="2">
<Setter TargetName="txt1" Property="Visibility" Value="Collapsed" />
<Setter TargetName="txt2" Property="Visibility" Value="Visible" />
<Setter TargetName="txt3" Property="Visibility" Value="Collapsed" />
</Trigger>
</StackPanel.Triggers>
<TextBlock Text="TextBlock 1" x:Name="txt1" />
<TextBlock Text="TextBlock 2" x:Name="txt2" />
<TextBlock Text="TextBlock 3" x:Name="txt3" />
</StackPanel>
Currently, the Error I am getting is "Property 'Mode' was not found in type 'StackPanel'. D:\tmp\WpfApplication1\TriggersAndProperties\MainWindow.xaml"
You need to use DataTriggers if you want triggers that can work on bindings. Problem is, DataTriggers are only available on style and template so you need to define one like this:
<StackPanel>
<StackPanel.Style>
<Style TargetType="{x:Type StackPanel}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Mode}" Value="1">
<Setter TargetName="txt1" Property="Visibility" Value="Visible" />
<Setter TargetName="txt2" Property="Visibility" Value="Collapsed" />
<Setter TargetName="txt3" Property="Visibility" Value="Visible" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Mode}" Value="2">
<Setter TargetName="txt1" Property="Visibility" Value="Collapsed" />
<Setter TargetName="txt2" Property="Visibility" Value="Visible" />
<Setter TargetName="txt3" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<TextBlock Text="TextBlock 1" x:Name="txt1" />
<TextBlock Text="TextBlock 2" x:Name="txt2" />
<TextBlock Text="TextBlock 3" x:Name="txt3" />
</StackPanel>
Another solution would be to use an IValueConverter that converts the int from Mode to the Visibility you want, and apply it directly to each text block Visibility property.
Note that Dean Chalk's answer stays valid: you have to use a DependencyProperty or implement INotifyPropertyChanged if you want changes on Mode to trigger.
Your propert 'Mode' needs to be a dependency property in order to be used this way:
public class MainViewModel : DependencyObject
{
readonly DependencyProperty ModeProperty = DependencyProperty
.Register("Mode", typeof(int), typeof(MainViewModel));
public int Mode
{
get { return (int) GetValue(ModeProperty); }
set { SetValue(ModeProperty, value); }
}
}