Moving a complicated XAML binding to a Style Setter - wpf

I'm using a checkbox with a custom style and control template. I would like to move the height and width bindings over to the style as well, but I can't seem to get it right.
Here's the checkbox XAML
<CheckBox Style="{StaticResource MyCheckStyle}"
HorizontalAlignment="Center"
Height="{Binding FontSize,
Source={x:Static gap:Settings.Default},
Converter={StaticResource MyMathConverter},
ConverterParameter='x+10'}"
Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"
IsChecked="{Binding ValueT, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
/>
I'm trying to achieve a check box whose height in pixels is 10 larger than the current font size in points (a hack but please ignore that for now).
I'm binding Width to ActualHeight so that they'll always be square (my custom template would allow any size otherwise)
I'm using a converter (Ivan Krivyakov's MathConverter) to do the math.
The size I'm binding to is stored in a static instance of a Settings class generated from a Settings file.
MyCheckStyle is currently as follows:
<Style x:Key="MyCheckStyle" TargetType="{x:Type CheckBox}">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Template" Value="{StaticResource MyCheckBoxTemplate}" />
</Style>
So far, this all works perfectly. But my attempt at moving the Width Binding to the style crashes the application:
<!-- WIDTH BINDING. CAUSES A CRASH DUE TO ACCESS VIOLATION -->
<Setter Property="Width">
<Setter.Value>
<Binding>
<Binding.Source>
<RelativeSource Mode="Self"/>
</Binding.Source>
<Binding.Path>
<PropertyPath Path="ActualHeight"/>
</Binding.Path>
</Binding>
</Setter.Value>
</Setter>
My attempts at the moving the Height binding are a complete failure. I can't figure out how to replicate the "x:Static" markup extension in element syntax, among other things. Intellisense/ReSharper isn't helping me much.
<Setter Property="Height">
<Setter.Value>
<Binding>
<!-- NO IDEA WHAT TO PUT HERE? -->
</Binding>
</Setter.Value>
</Setter>
</Style>
Anyone able to set me right? Is what I'm doing possible?

Have you ever done
Prop="{Binding Source={RelativeSouce ...}}"?
Nope. It's not Source, it's RelativeSource. The curly brace markup-extension syntax sets the same properties as the XML element syntax, but with curly braces. That's all. If it's RelativeSource with curly braces, it's RelativeSource with angle brackets.
<Setter Property="Width">
<Setter.Value>
<Binding>
<!-- RelativeSource, not Source -->
<Binding.RelativeSource>
<RelativeSource Mode="Self"/>
</Binding.RelativeSource>
<Binding.Path>
<PropertyPath Path="ActualHeight"/>
</Binding.Path>
</Binding>
</Setter.Value>
</Setter>
For x:Static, that's another markup extension (a subclass of System.Windows.Markup.MarkupExtension). If markup extension class names have the Extension postfix, the XAML parser lets you omit the "Extension" part. So as it happens that class is actually named StaticExtension. If you want static member Foo.Bar in the local namespace, easy:
<x:StaticExtension Member="local:Foo.Bar" />
The Binding class is a subclass of MarkupExtension (via BindingBase), which is why you can use the curly-brace syntax with it. However, it doesn't have the "Extension" name postfix. That's optional. There's no particular consistency there.
Key point: Every one of these things is a class. XAML is a notation for instantiating instances of classes and initializing their properties with either scalar values, or with other class instances. Curly-brace markup extension syntax is a funny little addition -- but a useful one, as you can see comparing the XAML above with what I'm about to suggest as a replacement.
Now, that's all well worth knowing, but you don't need any of it here.
tl;dr
<Setter
Property="Width"
Value="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"
/>
<Setter
Property="Height"
Value="{Binding FontSize,
Source={x:Static gap:Settings.Default},
Converter={StaticResource MyMathConverter},
ConverterParameter='x+10'}"
/>
Done.

Related

Stucturing a WPF gauge control

I have implemented a gauge control as a custom control in WPF. It is working very well, but I have hit an issue which is causing me to doubt whether it is structured it correctly.
Using different control templates, I can give the gauge radically different styles, for example like a speedometer, a voltmeter, a thermometer or an oil level.
The top level gauge control contains two collections, of Ranges and Pointers.
A Range defines a colored area on the scale. This is a simple object with a few properties.
A Pointer, fairly obviously, defines a pointer on the gauge. A guage can have multiple pointers, and the pointers can have differnt styles, for example a needle, a colored block or a slider (which the user can drag).
This is an example configuration:
<gauge:Gauge MinValue="0"
MaxValue="350"
MajorDivisions="7"
MinorDivisions="5"
Caption="Bar"
Background="White"
LabelOrientation="Horizontal"
BorderThickness="3"
StartAngle="-180"
EndAngle="135"
MinorTickMarkColor="Black"
MajorTickMarkColor="Black"
LabelsOnTicks="Even"
LabelRadiusRelative="0.65"
Template="{StaticResource DefaultRotaryGauge}" >
<gauge:Gauge.Ranges>
<gauge:Range MinValue="200" MaxValue="350" Color="Green"/>
</gauge:Gauge.Ranges>
<gauge:Gauge.Pointers>
<gauge:Pointer Position="{Binding Position1}" ShowNumericValue="Visible" Color="LightGray"/>
<gauge:Pointer Position="{Binding Position2}" ShowNumericValue="Visible" Color="Red"/>
</gauge:Gauge.Pointers>
<gauge:Gauge.LabelStyle>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="10"/>
<Setter Property="Foreground" Value="Black"/>
</Style>
</gauge:Gauge.LabelStyle>
</gauge:Gauge>
As shown above, the pointer position must work with binding, so it must be a dependency property and it must - as far as I understand - be in the visual tree.
For this purpose, the pointer class is defined as a control.
The control template for the gauge uses an ItemsControl to include the pointers. Each pointer has its own control template, but because there are different styes of pointer, I use a trigger to select one of several different templates for a pointer.
For example, my control template for a thermometer supports two pointer styles, which are selected as follows.
<ItemsControl ItemsSource="{TemplateBinding Pointers}"
Grid.Row="2"
Grid.Column="3"
ItemsPanel="{StaticResource GridTemplate}">
<ItemsControl.ItemContainerStyle>
<Style TargetType="local:Pointer">
<Setter Property="Control.Template" Value="{StaticResource ThermometerPointer}"/>
<Style.Triggers>
<Trigger Property="DisplayStyle" Value="Block">
<Setter Property="Control.Template" Value="{StaticResource ThermometerBlock}"/>
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
So far so good.
Now I have had a request, to change the colour of the liquid in the thermometer, when the value passes a given threshold. When I tried to implement this with a DataTrigger,
<gauge:Pointer Position="{Binding Position1}" DisplayStyle="Block">
<gauge:Pointer.Style>
<Style TargetType="gauge:Pointer">
<Setter Property="Color" Value="Red"/>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource GreaterThanConverter}">
<Binding Path="Position1"/>
<Binding Source="200.0"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Color" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</gauge:Pointer.Style>
</gauge:Pointer>
the pointer stopped working entirely, because it lost the connection to its control template.
I was able to fix it by specifying the Control.Template in addition to the DataTrigger.
<gauge:Pointer Position="{Binding Position1}" DisplayStyle="Block">
<gauge:Pointer.Style>
<Style TargetType="gauge:Pointer">
<Setter Property="Control.Template" Value="{StaticResource ThermometerBlock}"/>
<Setter Property="Color" Value="Red"/>
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource GreaterThanConverter}">
<Binding Path="Position1"/>
<Binding Source="200.0"/>
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Color" Value="Blue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</gauge:Pointer.Style>
</gauge:Pointer>
This works, but I'm not happy with it. The user of the guage control cannot know this detail about the internal impolementation.
I think it is probably bad practice for the control template of the gauge control to define a Style for the pointer. My problem is I can't figure out another way to select different control templates for the pointer.
I thought that I might be able to select a control template using a converter (converting my DisplayStyle property to a ControlTemplate), but I can see where I would apply the convertor.
My only other idea is to get rid of the DisplayStyle property and instead define multiple pointer classes which derive from a common base pointer class. I could then define separate control templates for each derived pointer class.
I have several questions.
Firstly, am I doing this right?
Does it even make sense to define the pointer as a control, with its own control template?
Secondly, is there a way to select the control template for the pointer, without requiring a style definition?
If not, is the idea with derived classes the way to go?

Why style targettype has to be rebinded back to the original properties?

I am referring to the code block here, on Data triggers
<Window x:Class="WpfTutorialSamples.Styles.StyleDataTriggerSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StyleDataTriggerSample" Height="200" Width="200">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<CheckBox Name="cbSample" Content="Hello, world?" />
<TextBlock HorizontalAlignment="Center" Margin="0,20,0,0" FontSize="48">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="No" />
<Setter Property="Foreground" Value="Red" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cbSample, Path=IsChecked}" Value="True">
<Setter Property="Text" Value="Yes!" />
<Setter Property="Foreground" Value="Green" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</Window>
To me, from programming language design point of view, the line <Style TargetType="TextBlock"> is completely unnecessary, because it is already nested inside the <TextBlock>, so of course whatever setter property should be applied to the TextBlock type. So why need the line <Style TargetType="TextBlock">?
Can <Style TargetType> be of other type except TextBlock?
So why need the line ?
A Style may be defined as a resource, i.e. not inline, and if doesn't have a TargetType specified, how is the XAML parser supposed to be able to parse it and set the properties specified by the setters? It can't. Or at least it doesn't.
Just because you can define a Style inline you are still creating an instance of the exact same class that may be used as a (global) resource and then setting a TargetType is indeed required.
Can be of other type except TextBlock?
No, apart from a type that is derived from TextBlock. If you specify another type you will get an exception at runtime when the BAML (the compiled XAML) is parsed.
You could use any class TextBlock derives from (for example FrameworkElement).
If you implement your own CustomizedTextBlock for example you are able to use styles defined for TextBlock in your project.
You find an example for this here.

DataTrigger Binding to a ToggleButton within another namespace

I try to access a ToggleButton that is within a separate UserControl to Trigger a DockPanel.Style DataTrigger.
Here is how I made it work when both, the ToggleButton and the DockPanel, are in the same namespace:
<ToggleButton x:Name="OneToggleButton"
Content="Click me..." />
<DockPanel>
<DockPanel.Style>
<Style>
<Setter Property="UIElement.Visibility"
Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked,
ElementName=DetailsBookToggleButton}"
Value="False">
<Setter Property="UIElement.Visibility"
Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DockPanel.Style>
<TextBlock DockPanel.Dock="Top" Text="..." />
</DockPanel>
But now when I move the ToggleButton into an other file (other namespace) it doesn't work anymore. ElementName (as I understand it) only works for elements within the same file.
So how can I manage a Binding to the IsChecked of my ToggleButton in another file?
Anybody have a suggestion? Would be great :)
FYI, the term you are looking for is "name scope", and there is no way to reference an element defined in another name scope. Arguably, you should not be allowed to do this.
Rather than binding one UI element to another, consider binding them both to a common property, either in your view model, on some common ancestor element, or via dependency property inheritance.

Why does data binding my chart series color fail?

I am drawing simple line charts using the WPF toolkit. My goal is to set the line color of my series via Data Binding. This succeeds only partially. The question is: why?
Setup
Namespaces:
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit" x:Class="WpfApplication3.MainWindow"
xmlns:media="clr-namespace:System.Windows.Media;assembly=PresentationCore"
Chart:
<chartingToolkit:Chart x:Name="chart">
<chartingToolkit:LineSeries x:Name="seriesEntries" IndependentValueBinding="{Binding Key}" DependentValueBinding="{Binding Value}" DataPointStyle="{StaticResource CommonLineSeriesDataPoint}">
<chartingToolkit:LineSeries.Tag>
<media:Brush>Green</media:Brush>
</chartingToolkit:LineSeries.Tag>
</chartingToolkit:LineSeries>
</chartingToolkit:Chart>
Ignore the Tag for now, it will be relevant later.
Notice the chart has a custom data point style, CommonLineSeriesDataPoint:
<Style x:Key="CommonLineSeriesDataPoint" TargetType="chartingToolkit:LineDataPoint">
<Setter Property="Background">
<Setter.Value>
<media:Brush>Red</media:Brush>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="chartingToolkit:LineSeries">
<Setter Property="DataPointStyle" Value="{StaticResource CommonLineSeriesDataPoint}" />
</Style>
As expected, this colors my line series red:
Breaking Change
Now I want to data bind my data point background. I make only one change. Instead of specifying the background brush directly, I bind it to the Tag property of my LineSeries, which is also a brush (see previous LineSeries declaration, it's a green one).
<Style x:Key="CommonLineSeriesDataPoint" TargetType="chartingToolkit:LineDataPoint">
<Setter Property="Background">
<Setter.Value>
<Binding Path="Tag" RelativeSource="{RelativeSource AncestorType={x:Type chartingToolkit:LineSeries}}" />
</Setter.Value>
</Setter>
</Style>
<Style TargetType="chartingToolkit:LineSeries">
<Setter Property="DataPointStyle" Value="{StaticResource CommonLineSeriesDataPoint}" />
</Style>
The result is this:
So the dots are green. But the line is gone.
My expectation is to see a green line as well! Where is it?
I found a solution after digging in the WPF Toolkit Sources.
Turns out the Stroke property of the series' Polyline is bound to a Background property via TemplateBinding. I suspect this doesn't go well with my try binding the Background property itself.
This answer on SO suggests that TemplateBinding is evaluated at compile time. So let's get rid of the TemplateBinding and bind the Stroke property directly to the Tag of my LineSeries (remember: the Tag contains the green brush).
From the WPF Toolkit Source \Source\DataVisualization\Themes\generic.xaml I copied part of the style definition for the LineSeries and added it to my ResourceDictionary:
<Style x:Key="CommonLineSeries" TargetType="chartingToolkit:LineSeries" BasedOn="{StaticResource {x:Type chartingToolkit:LineSeries}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="chartingToolkit:LineSeries">
<Canvas x:Name="PlotArea">
<Polyline Points="{TemplateBinding Points}" Stroke="{Binding Tag, RelativeSource={RelativeSource AncestorType={x:Type chartingToolkit:LineSeries}}}" Style="{TemplateBinding PolylineStyle}"/>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
(If interested you can search for <!-- charting:LineSeries --> in generic.xaml to find the source I copied from.)
The only thing I modified is the binding of Stroke. Here I use the same binding I used for my data points.
Last thing to do: tell the LineSeries to use this style:
<chartingToolkit:LineSeries x:Name="seriesEntries" IndependentValueBinding="{Binding Key}" DependentValueBinding="{Binding Value}" DataPointStyle="{StaticResource CommonLineSeriesDataPoint}" Style="{StaticResource CommonLineSeries}">
And lo and behold, it works. The line is back and it's green:
(If you look closely you see that the legend entry for the series still has the wrong color. But I assume the solution will be quite similar to the above.)

Binding to a cutom property of the template parent

Q: How do I bind to a custom property of the template parent from a child control's style DataTrigger
I've been scratching my head over this one for a couple of days.
I have a databound TreeView which uses a Style which has a Template. The TreeView is bound to a ObservableCollection and a HierarchicalDataTemplate + DataTemplate bind to properties inside a collection item.
FontGroup -> Font(s)
<Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}">
...
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Image x:Name="ExpanderImage" Source="/Typesee;component/Resources/tree_expand.png" RenderOptions.BitmapScalingMode="NearestNeighbor" />
<ControlTemplate.Triggers>
<DataTrigger Binding="??? IsItemSelected ???" Value="True">
<Setter TargetName="ExpanderImage" Property="Source" Value="/Typesee;component/Resources/tree_collapse_selected.png" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="FontTreeViewTemplate" TargetType="{x:Type TreeViewItem}">
...
<ToggleButton x:Name="Expander" Style="{StaticResource ExpandCollapseToggleStyle}" ... />
...
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsItemSelected}" Value="True">
<!-- WORKS FINE HERE -->
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
First I tried to bind like:
Binding Path=IsItemSelected, RelativeSource={RelativeSource TemplatedParent}
Then I read that might not work so I tried (including AncestorLevel 1+3):
Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}, AncestorLevel=2
Have also tried combos with UpdateSourceTrigger=PropertyChanged and Mode=TwoWay
If this is a flawed design please suggest a way of doing this: I basically want to change the image of the expand toggle button based on whether the property IsItemSelected is true on the TreeViewItem -- any ideas?
Thanks so much for any help!
The viewmodel in all likelihood will be the DataContext, so the binding should be a RelativeSource binding with a respective path which needs to explicity target the DataContext as the new source is the RelativeSource:
{Binding DataContext.IsItemSelected,
RelativeSource={RelativeSource AncestorType=TreeViewItem}}
As noted in my comment it might be advisable to extract this logic from the ControlTemplate as this leaves its bounds. One method would be subclassing the ToggleButton and exposing a public property for the image which then can be changed via a Style.

Resources