WPF Styling and Templating over many buttons? - wpf

I can't seem to get the correct combination to get the desired effect:
Current XAML:
<Button Content="Foo" prism:Click.Command="{Binding FooCommand}"
Visibility="{Binding IsEnabled, RelativeSource={RelativeSource Self}, Converter={ncon:VisibilityBooleanConverter}}" />
<Button Content="Bar" prism:Click.Command="{Binding BarCommand}"
Visibility="{Binding IsEnabled, RelativeSource={RelativeSource Self}, Converter={ncon:VisibilityBooleanConverter}}" />
I want to extract out the Visibility="{Binding IsEnabled, RelativeSource={RelativeSource Self}, Converter={ncon:VisibilityBooleanConverter}}" as a style to apply over all the Buttons (within this UserControl resources). I can't seem to get the correct combination going here to make that happen.
Basically, what it does is instead of just disabling the button based on the ICommand.CanExecute it takes that DependencyProperty and binds it to the Visibility of the Button using a boolean-visiblity converter so the button is not only disabled, but also collapsed.

Style would look like this i suppose:
<Style TargetType="{x:Type Button}">
<Setter Property="Visibility"
Value="{Binding IsEnabled, RelativeSource={RelativeSource Self}, Converter={ncon:VisibilityBooleanConverter}}"/>
</Style>
Doesn't that work?

<Style x:Key="ButtonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource Self}, Converter={ncon:VisibilityBooleanConverter}}" Value="Visible">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>

Related

Get selected TabControl name in DataTrigger binding

I have two TabControls in my window (tabQueryControl, and tabControl) and I've created style with a ContextMenu which I set to both the TabControls on a tab right click. However, depending on the tabcontrol which has been right clicked, I want to hide some context menu items. This is my code in the Style.
<Style x:Key="OutputContextMenuStyle" TargetType="{x:Type TextBlock}">
<Setter Property="ContextMenu" Value="{DynamicResource OutputContextMenu}"/>
</Style>
<ContextMenu x:Key="OutputContextMenu">
<MenuItem Header="View in DataViewer" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type cw:ChromeWindow}}, Path=DataContext.ViewCommand}" CommandParameter="OutputWindow">
<MenuItem.Icon>
<Image Source="/Data_Viewer;component/Resources/NodeIcons/view_in_dataviewer.png"/>
</MenuItem.Icon>
<MenuItem.Style>
<Style TargetType="MenuItem">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<!-- if the name of the parent tab control is tabQueryControl, we hide this context menu item -->
<DataTrigger Binding="{Binding Path=TabControl.Name, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}}" Value="tabQueryControl">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=TabControl.Name, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}}" Value="tabControl">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
</ContextMenu>
In the DataTrigger I try to get the name of the selected tab control, and set the visibility of the menu item depending on the name, but when I run the code the visibility is collapsed in both tabcontrols. I think the issue is in my binding for each data trigger.
First remove TabControl. from binding path.
After the binding works you can find, that the style doesn't work as expected. For both controls the menu item is visible. The problem is, that the triggers do work on the same ContextMenu instance.
To avoid it add x:Shared="false" to the ContextMenu:
<Style x:Key="OutputContextMenuStyle" TargetType="{x:Type TextBlock}">
<Setter Property="ContextMenu" Value="{DynamicResource OutputContextMenu}"/>
</Style>
<ContextMenu x:Key="OutputContextMenu" x:Shared="false">
<MenuItem Header="View in DataViewer" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type cw:ChromeWindow}}, Path=DataContext.ViewCommand}" CommandParameter="OutputWindow">
<MenuItem.Icon>
<Image Source="/Data_Viewer;component/Resources/NodeIcons/view_in_dataviewer.png"/>
</MenuItem.Icon>
<MenuItem.Style>
<Style TargetType="MenuItem">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<!-- if the name of the parent tab control is tabQueryControl, we hide this context menu item -->
<DataTrigger Binding="{Binding Path=Name, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}}" Value="tabQueryControl">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=Name, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}}" Value="tabControl">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
</ContextMenu>
So it should work as expected.

How to bind a DataTrigger to a non-bound attribute?

This is not in a template, it is a <Label> in the body of my XAML document. Notice that the Content of the label is set to 'PENDING'. This is monitoring a server connection and at various times the code-behind might change the value of the content to CONNECTED or ERROR. When that happens, I would like for the color of the text to change. I thought this would do it, but it doesn't... all I get is black text.
<Label x:Name="lbl_Connected" Content="PENDING" FontWeight="Bold" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Center">
<Label.Style>
<Style TargetType="Label">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource self}, Path=Content.Value}" Value="CONNECTED">
<Setter Property="Label.Foreground" Value="Green"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource self}, Path=Content.Value}" Value="PENDING">
<Setter Property="Label.Foreground" Value="Yellow"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource self}, Path=Content.Value}" Value="ERROR">
<Setter Property="Label.Foreground" Value="Red"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
Can someone tell me how I should be doing this?
Just remove the ".Value" part from the Binding's Path, i.e.:
<DataTrigger Binding="{Binding RelativeSource={RelativeSource self}, Path=Content}" Value="CONNECTED">
<Setter Property="Label.Foreground" Value="Green"></Setter>
</DataTrigger>
Anyway if I were you, I would use a Binding in order to set the Label content and a converter to handle the Foreground color.

Wpf TextBox style with tooltip

I want my text boxes to show a specific text as tool tip depending on the validation status. To do this I currently use the following code and it works:
<TextBox Name="textBox1">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="False">
<Setter Property="ToolTip" Value="{Binding XPath=HelpText/Description, Mode=OneTime}" />
</Trigger>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
When there is no error, a description text read from an xml-file is displayed and when there is an error the validation error string is displayed.
The problem is that now I have to copy this style and apply it to every textbox (and change the XPath, it is unique for each text box), what I would want to do is put the style in Window.Resources so that I can just reference it for every text box like this:
<TextBox Name="textBox2" Style="{StaticResource TextBoxStyle}" />
But the XPath will be different for each textbox so I cannot just place my style in Window.Resources. How can I make an instance specific XPath available in the style? Is there a way to do this without creating a new text box and derive from TextBox? I was wondering if one could use an attached property in this case but I don't know how they work.
Thanks in advance.
I haven't completely tested this, but you could use an attached property
Something like:
class ToolTipHelper {
public static readonly DependencyProperty ToolTipProperty =
DependencyProperty.RegisterAttached("ToolTip", typeof(string) ...
Then you modify your trigger:
<Trigger Property="Validation.HasError" Value="False">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(ToolTipHelper.ToolTip}, Mode=OneTime}" />
</Trigger>
And you use it like
<TextBox ToolTipHelper.ToolTip="{Binding XPath=HelpText/Description, Mode=OneTime}" />
You could use the Tag Property and change your style to
<Style TargetType="{x:Type TextBox}">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=Tag}" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>
Then you bind the desired XPath to the Tag property of each specific TextBox
<TextBox Tag="{Binding XPath=HelpText/Description, Mode=OneTime}" />

WPF DataTrigger Works for Image but not for Path

The following dims the image when I disable the button and show clearly when enabled:
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type UIElement}, AncestorLevel=1}, Path=IsEnabled}" Value="False">
<Setter Property="Opacity" Value="0.25"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
...
<Button Name="btnPageFirst" IsEnabled="False">
<Image Source="..\Resources\imgMoveFirst.png" />
</Button>
I want to do a similar effect but with Path. I want to gray the image. But it does not gray and there is no error.
<Style TargetType="{x:Type Path}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType={x:Type UIElement}, AncestorLevel=1}, Path=IsEnabled}" Value="False">
<Setter Property="Fill" Value="Gray"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
...
<Button Name="btnPageFirst" IsEnabled="False">
<Path Data="F1M820.557,535.025L838.189,535.024 817.857,555.36 857.82,555.36 857.82,568.301 817.998,568.301 838.226,588.531 820.557,588.499 793.82,561.765 820.557,535.025z" Stretch="Uniform" Fill="DodgerBlue" Width="16" Height="16" Margin="0,0,0,0" />
</Button>
There is a precedence order which is used to calculate the values of the dependency properties during runtime.
The oversimplified precedence list:
Local value (what you set on the control)
Triggers
Style setters
Your problem is that you set Fill="DodgerBlue" on your path and because it has higher precedence then the Trigger that is way you don't see the fill change. Also that is why it works for your Image because there you don't set the Opacity directly.
To make it work:
Remove the Fill="DodgerBlue" from your path
Set it in your style:
<Style TargetType="{x:Type Path}">
<Style.Setters>
<Setter Property="Fill" Value="DodgerBlue"/>
</Style.Setters>
<Style.Triggers>
<!-- ... -->
</Style.Triggers>
</Style>
As a side note: if you always "inside" in a button you can rewrite the RelativeSource to:
RelativeSource={RelativeSource AncestorType={x:Type Button}}

Automatically focusing parts of a ListBoxItem on selection

I have a ListBox which is populated from a collection of ViewModels, which uses in place editing, which I do by having a couple of styles which I can apply to parts of the DataTemplate which make them visible/collapsed as required. These look something like:
<Style
x:Key="UnselectedVisibleStyle"
TargetType="{x:Type FrameworkElement}">
<Setter
Property="Visibility"
Value="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}, Converter={StaticResource boolToVis}, ConverterParameter=False}" />
</Style>
<Style
x:Key="SelectedVisibleStyle"
TargetType="{x:Type FrameworkElement}">
<Setter
Property="Visibility"
Value="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}, Converter={StaticResource boolToVis}, ConverterParameter=True}" />
</Style>
With my ListBox having it's ItemTemplate given by something like:
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock
Text="{Binding Name}"
Style="{StaticResource UnselectedVisibleStyle}" />
<TextBox
x:Name="textBox"
Text="{Binding Name}"
Style="{StaticResource SelectedVisibleStyle}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
This works fine, but what I want ideally is to have the TextBox automatically selected when a user clicks the item, ideally in a nice generic way I can use throughout my project, and without too much messing around in my codebehind.
Thanks,
DM.
The following change to your selected Style seemed to work for me:
<Style x:Key="SelectedVisibleStyle" TargetType="{x:Type FrameworkElement}">
<Setter Property="Visibility" Value="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListBoxItem}}, Converter={StaticResource boolToVis}, ConverterParameter=True}"/>
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Setter Property="FocusManager.FocusedElement" Value="{Binding RelativeSource={RelativeSource Self}}"/>
</Trigger>
</Style.Triggers>
</Style>

Resources