Variables in XAML Style - c#

I have the following Style:
<Style x:Key="ButtonBase" TargetType="Button">
<Setter Property="Background" Value="#FF007BFF"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value=">
<Setter Property="Background" Value="Yellow" />
<Setter Property="Foreground" Value="Black" />
</Trigger>
<Trigger Property="IsPressed" Value="False">
<Setter Property="Background" Value="#FF007BFF" />
<Setter Property="Foreground" Value="White" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Setter.Value>
</Setter>
</Style>
and an inherited Style:
<Style x:Key="ElementButton" TargetType="Button" BasedOn="{StaticResource ButtonBase}">
<Setter Property="Margin" Value="10"/>
<Setter Property="Height" Value="200"/>
</Style>
What I'd like to do is be able to set an arbitrary variable in the base style:
<Setter Variable="HoverColor" Value="Pink"/>
Then I'd be able to use my triggers as such:
<Trigger Property="IsPressed" Value=">
<Setter Property="Background" Value="{TemplateBinding HoverColor}" />
<Setter Property="Foreground" Value="Black" />
</Trigger>
And finally, I could then override it in my inherited style:
<Style x:Key="ElementButton" TargetType="Button" BasedOn="{StaticResource ButtonBase}">
<Setter Property="Margin" Value="10"/>
<Setter Property="Height" Value="200"/>
<Setter Variable="HoverColor" Value="Red"/>
</Style>
Is there a way to achieve this? I've already looked at static resources but these can't be overridden. Also, I cannot use anything that requires a code-behind because I don't have one!

Its a good question and I've fought through this sort of thing as well. There may be some kind of XAML-only approach that could work, but I have a feeling that if there is, it would feel pretty kludgy. I have a couple of suggestions of how achieve what you want.
First, a quick observation. You say you "don't have a code-behind" and that your view is "XAML-only". Well, I've never seen a UserControl View that doesn't have any code-behind file at least, so I'm assuming you mean you just don't want to put any code in there (other than the obligatory InitializeComponent()). Having said that, the approaches that I'll outline won't require code in your code-behind files.
At the end of the day, it sounds like what you really want is define some custom "variables". These suggestions do just that, albeit maybe not in the way that you originally envisioned doing so.
The first approach that would solve your problem is to subclass the control that you are interested in styling and add any custom dependency properties to it. For example, you could subclass Button, to say something like ButtonWithMyVariables. One of those custom dependency properties would be called "HoverColor", of type Color, or perhaps more appropriately, "HoverBrush" of type Brush (if you're wanting to just apply it directly to the background or foreground property). Then your base Style can set HoverColor to whatever it wants, and your inherited Style can override it, or you can override it directly on the element in your XAML. I'm not providing code samples for this approach (right now, unless requested) since this is a more commonly-used approach that I'm guessing you're already familiar with.
The second approach would be to define a custom attached property. I've not seen this approach used as much for dealing strictly with Styling issues, perhaps because for the attached "behavior" to fully do its job in this case, authors have used Style files to react (bind to) and apply visual changes based on the attached property, rather than the code in the attached property changed callback doing something stylistically (but I suppose it could still be done that way). However, this approach feels "lighter weight" to many, since you don't need to subclass any existing controls.
An example of this second approach can be found in the MahApps.Metro library, specifically the TextboxHelper class (which houses attached properties) and the Controls.TextBox.xaml style file (which binds to those attached properties). For example, we see in the control template for the TextBox, this line that makes use of the Watermark attached property:
<TextBlock x:Name="Message"
Text="{TemplateBinding Controls:TextboxHelper.Watermark}" Visibility="Collapsed"
Foreground="{TemplateBinding Foreground}" IsHitTestVisible="False" Opacity="0.6" HorizontalAlignment="Left" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="6,0,0,0"/>
Now you can imagine that you could set that Watermark value in a base style to something:
<Setter Property="Controls:TextboxHelper.Watermark"
Value="My helpful watermark for all!"/>
And then override it in an inherited style:
<Setter Property="Controls:TextboxHelper.Watermark"
Value="A more specific watermark!"/>
With either approach, we can define any "variable" we want and easily set them in a Style setter, override them in inherited styles, TemplateBind to them within control templates, or Trigger off of them.

Related

Override style on parts of ComboBox scrollbar

After lots of research, I stumbled across a relatively simple way to target just specific parts of the control style without using the entire control template. It's partially successful, but I need a little help getting all the way to the end.
Specifically, I am trying to override the Thumb button color of the scrollbar in the dropdown of a ComboBox. The cool technique I came across is the following, which utilizes nested Style.Resources to access the lower objects...
<Style x:Key="MyComboBoxStyle" TargetType="{x:Type ComboBox}">
<Style.Resources>
<Style TargetType="ScrollViewer">
<Style.Resources>
<Style TargetType="ScrollBar">
<Setter Property="Background" Value="LightGreen" />
<Style.Resources>
<Style TargetType="Track">
<Setter Property="Cursor" Value="Cross" />
<Style.Resources>
<Style TargetType="Thumb">
<Setter Property="Background" Value="Red"/>
<Setter Property="Cursor" Value="Hand" />
</Style>
</Style.Resources>
</Style>
<Style TargetType="RepeatButton">
<Setter Property="Background" Value="Red"/>
<Setter Property="Cursor" Value="Hand" />
</Style>
</Style.Resources>
</Style>
</Style.Resources>
</Style>
</Style.Resources>
<!--- rest of ComboBox style definition follows... -->
By sequentially drilling down into the complex control style tree, using nested Style.Resources, I am able to target specific aspects of a control style, without needing the entire style definition.
The xaml code above successfully drills all the way down to the "Track"... I can change things like the margin, cursor, etc. of the Track. But, I just can't seem to get that last step to the Thumb. Also, I can't seem to access the Repeat buttons, which should be at the same level (in the object tree) as the Track.
Looking at the style template for ScrollBars seems to show that the object tree is ScrollBar->Track->Thumb... but I seem to be missing something?
Any ideas on how to get access to the Thumb color?
The default style for the ScrollBar sets the Style property of the Thumb explicitly in the Track:
<Track x:Name="PART_Track" ...>
...
<Track.Thumb>
<Thumb Style="{StaticResource ScrollBarThumbVertical}"/>
</Track.Thumb>
</Track>
This means that your implicit Thumb style won't be applied.
So you will have to define a custom complete ControlTemplate for the ScrollBar to be able to modify the style/template of the Thumb.
Alternatively, you may consider to look it up in the visual tree at runtime and set any of its properties programmatically.

"Enter" key not leaving cell in RadGridView

All around my project I'm facing similar problem, which is "Enter" key, creating new line in cell, instead of move to next line.
My current telerik version is 2018.1.122.45, and default, expected behavior is to leave cell, after pressing "Enter" key (according to telerik documentation, and helpdesk).
However, in my case it always makes new line within cell being edited.
I'm using Visual Studio 2013 theme, my implementation of RadGridView is correct, I've pasted my RadGridView, to project, I got from telerik support, and there, Enter was working as expected. Also, they've pasted my RadGridView implementation to their project and it also worked correctly.
Have anyone faced similar problem? I'm looking for solution, since I can't track source of this issue (even with teleriks help).
I've found a solution to this problem, and other Styles problems. Implementing style in the way demonstrated in telerik documentation (f.e. https://docs.telerik.com/devtools/wpf/controls/radgridview/styles-and-templates/styling-a-row) has some issues not mentioned in documentation.
<Style TargetType="telerik:GridViewRow">
<Setter Property="Background" Value="Red"/>
<Setter Property="Foreground" Value="White"/>
</Style>
This is one of the simpliest examples of implementing style. In my case it was:
<Style TargetType="telerik:GridViewCell"
x:Key="IloscNormalStyle"
BasedOn="{StaticResource GridViewCellStyle}">
<Setter Property="Background"
Value="#c3d8c7" />
<Setter Property="Foreground"
Value="Black" />
</Style>
And it works just fine. The biggest issue is that it completly ignores implemented Theme for project and all it's behaviours, which are, for example, selection behavior, enter key press, borders, etc. In order to tell style to not ignore implemented theme, I needed to insert this code to my styles:
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=telerik:GridViewRow}}"
Value="True">
<Setter Property="Background"
Value="{Binding Background}" />
</DataTrigger>
</Style.Triggers>
Which finally made my style works with desired behavior. Full style code:
<Style TargetType="telerik:GridViewCell"
x:Key="IloscNormalStyle"
BasedOn="{StaticResource GridViewCellStyle}">
<Setter Property="Background"
Value="#c3d8c7" />
<Setter Property="Foreground"
Value="Black" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=telerik:GridViewRow}}"
Value="True">
<Setter Property="Background"
Value="{Binding Background}" />
</DataTrigger>
</Style.Triggers>
</Style>
I think it is a major issue for telerik (or maybe even WPF), but this couple lines of code resolves most of problems with custom cell/row styling.

Give a toggle button the color "#FF5D0000" in the main window

I want to give mij text on my button a customized color in the MainWindow.xaml.cs
Normally you give the color in the cs file by this way to the command:
ToggleButton.Foreground = Brushes.Green;
But I want to give the hexnumber
I've already tried something like this :
SolidColorBrush Owncolor = (SolidColorBrush)(new BrushConverter().ConvertFrom("#FF5D0000"));
ToggleButton.Foreground = Brushes.Owncolor;
Instead of doing it in code behind (unless you have a very specific reason to do that), you can work on your xaml
<ToggleButton Foreground = "#FF5D0000"/>
if you are doing it based on some condition, also please take a look at this. It's always a better practice to handle graphical stuff in your xaml as much as you can
for instance you can do
<ToggleButton>
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Foreground" Value="Green"/>
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
Usually in WPF you tend to use styles defined in XAML to change how controls looks. However, using the BrushConverter works if you absolutely have to use the hexadecimal syntax in codebehind. I'd consider building a new SolidColorBrush with Color.FromArgb easier, but that also works.
As for how to use styles and XAML properly, you should probably read some tutorials or books. WPF is quite a different beast than Windows Forms or a lot of older UI frameworks, so there's some re-learning required.
The simplest way of achieving what you want (a different text colour when the button is pressed) would be the following style:
<Style TargetType="ToggleButton">
<Setter Property="Foreground" Value="#FF5D0000"/>
<Style.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Foreground" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
When placed in the Resources of your Window it would apply to all ToggleButtons in that window.

Binding to value of property from inside style template failing

I'm having an issue with attempting to bind the Fill property of an Elipse (in a ToggleButton control) to a custom DependencyProperty of another control.
Below is the XAML code raising the "Property path is not valid" error - It's a Resource dictionary file for the Expander control.
The erroneous line:
Value="{Binding Path=(local:Expander.ToggleButtonMouseoverColor)}"
The first code block - the MixSelectorExpanderButtonStyle is attached to a style definition for an expander, seen in the second code block.
<Style x:Key="MixSelectorExpanderButtonStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Circle" Property="Fill" Value="{Binding Path=(local:Expander.ToggleButtonMouseoverColor)}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I'm confident the property is correct - in the same file, I am able to access the ToggleButtonMouseoverColor property without error:
<Style TargetType="{x:Type local:Expander}">
<Style.Triggers>
<Trigger Property="Status" Value="0">
<Setter Property="ToggleButtonMouseoverColor" Value="{DynamicResource ZKGeneric_Highlight_MouseOver}" />
</Trigger>
</Style.Triggers>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Expander}">
<ToggleButton style=""{StaticResource MixSelectorExpanderButtonStyle}" />
</ControlTemplate>
</Setter.Value>
</Setter.Value>
</Style>
When I remove the local: prefix from the path, the error changes to the property not being recognized or accessible.
If I name the style definition and change the binding to:
Value="{Binding Path=ExpanderStyle.ToggleButtonMouseoverColor}"
I am able to build and run it, however it appears to just be a null value.
Clearly I'm missing some syntax to properly point to the property, but this is the first time i have attempted to bind in such a convoluted manner.
If not, is there a more optimal way to define this behaviour?
Any help would be appreciated, in the meantime, I shall attempt to consult my WPF programmer's reference.
EDIT:
I forgot to mention, I have also been trying to use Value="{Binding ToggleButtonMouseoverColor, RelativeSource={RelativeSource TemplatedParent}}", among other variations, but can't seem to get it working.
I believe you want this, although it sounds like you've potentially tried it? I have no idea why it wouldn't work...
Value="{Binding ToggleButtonMouseoverColor, RelativeSource={RelativeSource AncestorType={x:Type local:Expander}}}"

How do I know whether to use a style or override a control template?

This question is inspired by this recent question and other situations I've encountered in my WPF development. How do I know whether it is enough to set a style on a control to override some default behavior vs creating a new control template?
More concretely, in the question above, the author wants to change the look of a ListBoxItem when it is selected. (See code reprinted below). Everything works, except the Background property. How is one supposed to know that they should override the Control Template for this?
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Content" Value="{Binding Path=Name}"/>
<Setter Property="Margin" Value="2"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
As to whether to use a style or template Ray provided a great response.
As to how to solve your problem without creating a template, maybe I can help.
The background color is being set by the SystemColors. Using Blend and creating a template you can see the exact xaml.
So if NO TEMPLATES! is a requirement you can always change what that resource is.
Example :
<ListBox>
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Yellow" />
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Content" Value="{Binding Path=Name}"/>
<Setter Property="Margin" Value="2"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="Foreground" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
<ListBoxItem>Test 1</ListBoxItem>
<ListBoxItem>Test 2</ListBoxItem>
<ListBoxItem>Test 3</ListBoxItem>
</ListBox>
That will give you the background color for that given ListBox and not screw up anything else in the app.
Styles can be thought of very closely to CSS styles in HTML. If all you want to do is change the basic properties of a control such as Background, Foreground or whatever properties it exposes then a Style is exactly what you need. Styles also allow you to apply triggers so for animations, a style is also sufficient.
If you're finding you want to change the intrinsice behaviours / inner workings on a control then a control template is what you want. For example, if you want to change how a button is laid out by adding some sort of grid behaviour, then using a control template is the way forward.
Unfortunately, for your specific example, you don't know unless you try it. Basically you first try it with a Style....and if that doesn't work for whatever reason, then you write a ControlTemplate. You usually only end up writing ControlTemplates for the reasons Ray mentioned.
My guess is that the trigger you're trying to set has also been hardcoded in the ControlTemplate...which is bad design imo because it prevents the Style from overriding it.
By "Background" I take it to mean the "blue" rectangle that surrounds the ListBoxItem when it is selected?
This is actually the FocusVisualStyle property, which is a style that describes what the item should look like when it is focused. The Control explicitly sets this property (described here), so in order to override it, you will have to redefine the Control Template, making sure to use a default Style setter to set it to {x:Null}.

Categories