Can I reuse a Style using different DataTrigger Bindings? - c#

I am working on a WPF 4.0 window with many status indicators using Labels. In one example, the Labels turn GREEN and read "Available" if the bound property's enumeration Foo is AVAILABLE. The labels turn RED and read "Not Available" if the bound property is NOTAVAILABLE. I also have other labels that indicate status of different bound enum Bar, and they turn different colors and have different content based on this value.
I am able to successfully bind and turn one label's color and text based on the value of a bound property. I use a rather lengthy ControlTemplate in a DataTrigger's Setter just to change the label's text.
Here's what I have so far:
<Window> ...
xmlns:cst="clr-namespace:CstCommonTypes;assembly=CstCommonTypes"
...
</Window>
<Label x:Name="Avail_Out_LBL" HorizontalAlignment="Left" Margin="111,44,0,0" VerticalAlignment="Top" Width="124" Height="18" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" SnapsToDevicePixels="False" Grid.Column="1" Padding="0">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Setter Property="Background" Value="#FFC08100"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="24"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Degraded"
Grid.Column="1"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
Foreground="{TemplateBinding Foreground}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding MonitorAndControlData.Availability}" Value="{x:Static cst:Foo.Available}">
<Setter Property="Background" Value="#FF567E4A"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="24"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Available"
Grid.Column="1"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
Foreground="{TemplateBinding Foreground}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding MonitorAndControlData.Availability}" Value="{x:Static cst:Foo.NotAvailable}">
<Setter Property="Background" Value="LightCoral"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="22"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="22"/>
</Grid.ColumnDefinitions>
<TextBlock Text=" Not Available"
Grid.Column="1"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
Foreground="{TemplateBinding Foreground}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
The above works, but I was wondering if I can reuse either the Style and give it different DataTrigger bindings or just reuse the ControlTemplate to cut down on xaml code. I tried to see if I could define a resource, but I couldn't figure out how to give it different Template Bindings for all the Labels.
Any help would be greatly appreciated.

If you need more properties to bind to, then turn it into a custom control (i.e. a class that inherits from Control), then you can add additional dependency properties on said control, which you can then bind to in the template.

It won't be possible to reuse the style if the bindings are not the same.
What you could do is make yourself a user control for your status indicators labels, Inside which you will create a Dependency Property (lets name it Availability) on which you will bind your Triggers to. But, you will have to make sure that you will always need the same type for your triggers. If the Availability binding is not of the same type for each of your labels, this solution won't work.
Here is a link on how to use Dependency Properties :
http://www.wpftutorial.net/dependencyproperties.html

Related

Resizing Checkboxes in Windows Universal Application

I have crowded Page in my project and I want to place too many Checkbox control in a single page, so I'm looking for a way to resize my checkboxes.
When I change height and width of my checkbox, some part of its text disappear, In other word I need to scale my checkbox and make some tiny checkbox.
You can of course scale it down by using ScaleTransform but modifying its style gives you more flexibility.
Here's an example -
<Style x:Key="TinyCheckBoxStyle"
TargetType="CheckBox">
<Setter Property="Padding"
Value="4,0,0,0" />
<Setter Property="HorizontalContentAlignment"
Value="Left" />
<Setter Property="FontSize"
Value="11" />
<Setter Property="MinWidth"
Value="0" />
<Setter Property="MinHeight"
Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CheckBox">
<Grid x:Name="RootGrid"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<VisualStateManager.VisualStateGroups>
<!-- Add default visual states back in here -->
</VisualStateManager.VisualStateGroups>
<Grid>
<Rectangle x:Name="NormalRectangle"
Fill="{ThemeResource CheckBoxCheckBackgroundFillUnchecked}"
Height="12"
Stroke="{ThemeResource CheckBoxCheckBackgroundStrokeUnchecked}"
StrokeThickness="{ThemeResource CheckBoxBorderThemeThickness}"
UseLayoutRounding="False"
Width="12" />
<FontIcon x:Name="CheckGlyph"
Foreground="{ThemeResource CheckBoxCheckGlyphForegroundUnchecked}"
FontSize="{TemplateBinding FontSize}"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
Glyph=""
Opacity="0" />
</Grid>
<ContentPresenter x:Name="ContentPresenter"
AutomationProperties.AccessibilityView="Raw"
ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTransitions="{TemplateBinding ContentTransitions}"
Content="{TemplateBinding Content}"
Grid.Column="1"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
TextWrapping="Wrap"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Basically, you need to reduce the size of NormalRectangle, CheckGlyph and the FontSize. Note I have removed the visual states to simplify the answer, you just need to add them back from the default style.

Can't access the Text property of a TextBlock in a ControlTemplate

I'm trying to data bind a property to a Label and change its color and text in response to the value of the bound property. I'm using a ControlTemplate to change the color and Text because changing the Content of a Label in response to DataTriggers didn't work (the text never appeared).
So, using a ControlTemplate works when defining it inline on the Label, but does not seem to work when defining the template as a resource.
The code below is a simpler example to demonstrate the problem.
This is what I have so far:
<ResourceDictionary>
<ControlTemplate x:Key="baseTemplate" TargetType="{x:Type Label}">
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="24"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="InnerTextBlock" Grid.Column="1"
Text="{TemplateBinding Label.Content}" <!-- An attempt to tie the Text here to the Label's Content property -->
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
Foreground="{TemplateBinding Foreground}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
/>
</Grid>
</ControlTemplate>
<Style x:Key="availableLabelStyle" TargetType="{x:Type Label}">
<Setter Property="Background" Value="#FF567E4A"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Content" Value="Available"/>
<Setter Property="Template" Value="{StaticResource baseTemplate}"/>
</Style>
</ResourceDictionary>
<Label x:Name="StatusLabel"
Style="{StaticResource availableLabelStyle}"
Grid.Column="1"
HorizontalAlignment="Left"
Margin="111,71,0,0"
VerticalAlignment="Top" Width="124"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Height="18"
Padding="2"
/>
The problem is that the Content property in the Setter for the 'availableLabelStyle' does not seem to work. No text appears when this style is applied to a Label.
Is there a better way to do the same thing here AND get the text to appear in the Label?
Thanks in advance for any help on this.
The code you have is working. Here is my complete example:
<Window x:Class="WPFTestApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ResourceDictionary>
<ControlTemplate x:Key="baseTemplate" TargetType="{x:Type Label}">
<Grid Background="{TemplateBinding Background}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="24"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" Text="{TemplateBinding Label.Content}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
Foreground="{TemplateBinding Foreground}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"/>
</Grid>
</ControlTemplate>
<Style x:Key="availableLabelStyle" TargetType="{x:Type Label}">
<Setter Property="Background" Value="#FF567E4A"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Content" Value="Available"/>
<Setter Property="Template" Value="{StaticResource baseTemplate}"/>
</Style>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Label x:Name="StatusLabel" Style="{StaticResource availableLabelStyle}"
Grid.Column="1"
HorizontalAlignment="Left"
Margin="111,71,0,0"
VerticalAlignment="Top" Width="124"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Height="18"
Padding="2"/>
</Grid>
</Window>
Which produces the following output:
Another way to do this is to use ContentPresenter instead of TextBlock. You can still add all of your additional properties to it (at least the ones you have shown), and it would allow displaying content other than text.

Tooltip control with multiple Content properties

I am designing my own tooltip in SilverLight 5 and need to pass several values to it when displaying it.
Here is the Style:
<Style x:Key="TooltipStyle" TargetType="ToolTip">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Border BorderBrush="Blue" BorderThickness="2" Background="White">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Var Number: "></TextBlock>
<ContentPresenter x:Name="Content1"
Content="{TemplateBinding Content}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</StackPanel>
<StackPanel Grid.Row="1">
<TextBlock Text="Last Update Date: " />
<ContentPresenter x:Name="Content2"
Content="{TemplateBinding Content}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</StackPanel>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I am applying the style like so:
var customTooltip = new ToolTip
{
Style = (Style)Resources["TooltipStyle"],
Content = questions.Number[c]
};
ToolTipService.SetToolTip(textbox, customTooltip);
There is only one 'Content' property there, but I need to pass something to 'Content2' as well. (Please note the content is gathered as we do a 'for' loop.)
So the image that comes up, instead of having one variable, can have both the Var Number and the Last Update Date. Reputation too low to post image, here is the final look of the tooltip to give you an idea:
http://imgur.com/HYBbXMN
So that's the situation.
Now I am wondering if I can expose a second Content property? Or perhaps there is a smarter and better way to style the tooltip to meet my needs?
Please note this example requires two values (or 'Content') to be displayed but tooltip will expand to require more.
I will appreciate any ideas.
So to make it easier on ya, personally I just leverage things already available for that type of thing. Like for example using Tag to piggy back that sort of thing into the template.
An example (with some additions for setters and stuff you'll probably want to omit or change) like;
<Style x:Key="NiftyToolTipStyle" TargetType="ToolTip">
<Setter Property="FontFamily" Value="{StaticResource FontFamily}" />
<Setter Property="FontSize" Value="{StaticResource FontSize}" />
<Setter Property="Foreground" Value="Black" />
<Setter Property="Background" Value="White" />
<Setter Property="Padding" Value="3" />
<Setter Property="BorderThickness" Value="1,2" />
<Setter Property="BorderBrush" Value="Blue" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Border x:Name="Root"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="1"
Padding="{TemplateBinding Padding}"
Cursor="{TemplateBinding Cursor}">
<Border.Effect>
<DropShadowEffect Opacity="0.35" ShadowDepth="3" />
</Border.Effect>
<TextBlock>
<Run Text="{TemplateBinding Tag, StringFormat='Var Number: \{\0}'}"/>
<LineBreak/>
<Run Text="{TemplateBinding Content, StringFormat='Last Update Date: \{\0}'}"/>
</TextBlock>
<!--
<ContentPresenter Margin="{TemplateBinding Padding}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Cursor="{TemplateBinding Cursor}" />
-->
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This way you can just pass in one value to Content and another to Tag and you're good. You could also easily do other stuff, there's multiple options. Hope this helps, Cheers

How to get control used in template in user control code?

How could I get a DataGrid control used in xaml style template to use it in code?
<UserControl>
<UserControl.Resources>
<Style x:Key="MyComboBoxStyle" TargetType="{x:Type ComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<Grid x:Name="MainGrid" SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="0" MinWidth="{DynamicResource {x:Static SystemParameters.VerticalScrollBarWidthKey}}" />
</Grid.ColumnDefinitions>
<Popup x:Name="PART_Popup"
Grid.ColumnSpan="2">
<Microsoft_Windows_Themes:SystemDropShadowChrome x:Name="Shdw">
<Border x:Name="DropDownBorder">
<DataGrid x:Name="PART_PopupDataGrid" />
</Border>
</Microsoft_Windows_Themes:SystemDropShadowChrome>
</Popup>
<ToggleButton Grid.ColumnSpan="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
IsChecked="{Binding Path=IsDropDownOpen,
Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource ComboBoxReadonlyToggleButton}" />
<ContentPresenter Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
IsHitTestVisible="false"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<ComboBox Name="ComboGrid" Style="{DynamicResource DataGridComboBoxStyle}" />
</Grid>
</UserControl>
There is how I tried to get DataGrid control in user control, but not successfully:
var v1 = this.FindName("PART_PopupDataGrid");
var v2 = this.Template.FindName("PART_PopupDataGrid", this);
var v3 = ComboGrid.FindName("PART_PopupDataGrid");
How could I get this control in code?
This is a very common requirement. In fact, it's so common that Microsoft even have a page on MSDN specifically for this:
How to: Find ControlTemplate-Generated Elements
Basically, if you have a reference to the ComboBox that the ControlTemplate is applied to (let's call it ComboBox), then you should be able to do this:
DataGrid dataGrid =
ComboBox.Template.FindName("PART_PopupDataGrid", ComboBox) as DataGrid;

Binding custom dependency property to custom WPF style

I have an issue when designing a inherited Expander. My intention is to have a progress bar behind the toggle button and text in the default Expander header.
I have this XAML code which gives me the progress bar in the header. It is a custom style.
<Style x:Key="CurrentScanExpanderStyle" TargetType="{x:Type local:ProgressExpander}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ProgressExpander}">
<Border SnapsToDevicePixels="true" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="3">
<DockPanel>
<Grid DockPanel.Dock="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ProgressBar Name="ProgressBar"/>
<ToggleButton FontFamily="{TemplateBinding FontFamily}" FontSize="{TemplateBinding FontSize}" FontStretch="{TemplateBinding FontStretch}" FontStyle="{TemplateBinding FontStyle}" FontWeight="{TemplateBinding FontWeight}" Foreground="{TemplateBinding Foreground}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" FocusVisualStyle="{StaticResource ExpanderHeaderFocusVisual}" Margin="1" MinHeight="0" MinWidth="0" x:Name="HeaderSite" Style="{StaticResource ExpanderDownHeaderStyle}" IsChecked="{Binding Path=IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Content="{TemplateBinding Header}" ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}"/>
</Grid>
<ContentPresenter Focusable="false" Visibility="Collapsed" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" x:Name="ExpandSite" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" DockPanel.Dock="Bottom"/>
</DockPanel>
</Border>
<ControlTemplate.Triggers>
<!-- Triggers haven't changed from the default -->
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
this works fine but I am having trouble binding my custom dependency property which controls the progress percentage.
public class ProgressExpander : Expander
{
static ProgressExpander()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ProgressExpander), new FrameworkPropertyMetadata(typeof(ProgressExpander)));
}
public int Progress
{
get { return (int)GetValue(ProgressProperty); }
set { SetValue(ProgressProperty, value); }
}
// Using a DependencyProperty as the backing store for Progress. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ProgressProperty =
DependencyProperty.Register("Progress", typeof(int), typeof(ProgressExpander), new UIPropertyMetadata(0));
}
This is the code inside of the window:
<local:ProgressExpander Grid.Row="1" Header="Current Scan" ExpandDirection="Down" x:Name="currentScanExpander" Style="{DynamicResource CurrentScanExpanderStyle}">
<Canvas Background="SkyBlue"
Name="currentScanCanvas"
Height="{Binding ElementName=currentScanExpander, Path=ActualWidth}"
/>
</local:ProgressExpander>
I'm not sure how to bind this dependency property progress to the progress value in the ProgressBar within the style.
Any help would be appreciated.
In the style, we can use a standard Binding with a RelativeSource to set up the property.
<ProgressBar Name="ProgressBar"
Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Progress}"
Minimum="0"
Maximum="100" />
Then, in the window we just add Progress="50" or a binding to somewhere else.
You will also need to set the Button's background to transparent, or change some manner of the layout, in order to see it.

Categories