Binding int variable as value in style - c#

I am trying to find how to bind an int variable as my value in style. Here is my simple xaml style.
<Style TargetType="Button" x:Key="ButtonMenu">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="FontSize" Value="14"/>
<Setter Property="Foreground" Value="White"/>
</Style>
In the fourth row, I wanna change the value from 14 to my variable called SizeVar bud I can't find how to do it. I really tried googling but can't find anything that works for me. I know that I should do it somehow through binding but not exactly how.
I tried using different binding options but couldn't find one that works for me. My English is also not perfect so maybe it is possible that I misunderstood something.

From your description I interpreted the following:
You have a XAML-file where in the Resources you want to contain a style called "ButtonMenu".
The variable that binds to the FontSize is called "SizeVar" and is contained in the DataContext, which is set.
With this, I tested it out and the following works.
In the View (XAML):
...
<Window.DataContext>
<viewmodels:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<Style TargetType="Button" x:Key="ButtonMenu">
<Setter Property="FontSize" Value="{Binding SizeVar}"/>
</Style>
</Window.Resources>
<Grid>
<Button
Content="Test"
Style="{DynamicResource ButtonMenu}"/>
</Grid>
...
In the associated ViewModel (C#):
private int _sizeVar;
public int SizeVar
{
get => _sizeVar;
set
{
_sizeVar = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
SizeVar = 14;
}

Related

How to get a string from c# code to XAML (WPF)

I am trying to get a string from my C# code over to my XAML, but I can't seem to find a way to do it
My C# code
public string demoColour= "#FFFFFF";
My XAML Code
...
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" TargetName="Bd" Value="{ NOT SURE WHAT GOES HERE :( }"/>
</Trigger>
...
As Clemes said, you should take a look at data binding.
Data binding is one really important thing in WPF.
But here is one Solution, which works fine:
Make a new Class called ViewModel
Add a Property to that Class like public string MyColor { get; set; } = "#FFFFFF";
Set the DataContext in your XAML:
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
Bind your Property to your XAML whereever you want. For Example:
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Background" Value="{Binding MyColor}"/>
</Style>
</Window.Resources>

Create A Custom Event That Can Be Triggered From Code Behind

I'm breaking my pick on something I would have thought to be pretty simple. We have an application which uses some advanced color animations on a panel or a border control to attract the users attention when specific conditions occur. Usually those conditions are identified by communicating with an external device and could occur at any time. So I want to be able to define a trigger for a border or stackpanel like I might for it's standard IsEnabled or IsFocused properties but one that I can trigger arbitrarily form the Main Window's code-behind (IsMyArbitraryCondition). I imagine it would look like this:
<Border x:Name="Fancy_Border">
<Border.Style>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="5"/>
<Setter Property="BorderThickness" Value="5"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<Trigger Property="IsMyArbitraryCondition" Value="true">
<Trigger.EnterActions>
<BeginStoryboard x:Name="Board" Storyboard="{StaticResource ComplicatedAnimation}"/>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
Then in the code-behind for the main window:
Fancy_Border.IsMyArbitraryCondition = true;
// ComplicatedAnimation occurs
I've seen attached properties, dependency properties, etc suggested but my level of knowledge about how to us WPF has been insufficient so far to use those examples successfully. It seems like there must be a relatively simple way that I can still define my complex trigger behavior with the convenience of an XAML style but have that style trigger fired by something (anything!) other than the built in properties (IsMouseOver, IsPressed, IsVisible, etc etc).
EDIT (Using UserControl And DataTrigger)
I have a complete and working example provided below. The only remaining issue is that I can't put named components inside the content of my extended border control. The compiler returns:
Cannot set Name attribute value 'Example' on element 'TextBlock'. 'TextBlock' is under the scope of element 'HighlightBorder', which already had a name registered when it was defined in another scope.
This is an issue as I'm not developing using MVVM and seek to directly manipulated some individual controls that are within the HighlightBorder. Any workarounds you know of for that would be very helpful!
HighlightBorder.cs
using System;
using System.Windows;
using System.Windows.Controls;
namespace App
{
public partial class HighlightBorder : Border
{
public HighlightBorder()
{
InitializeComponent();
}
public bool IsActiveHighlight
{
get { return (bool) GetValue(ActiveHighlightProperty); }
set { SetValue(ActiveHighlightProperty, value); }
}
public static readonly DependencyProperty ActiveHighlightProperty =
DependencyProperty.Register("IsActiveHighlight", typeof(bool), typeof(HighlightBorder), new UIPropertyMetadata(null));
}
}
HighlightBorder.xaml
<Border x:Class="App.HighlightBorder"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Border.Style>
<Style TargetType="Border">
<Setter Property="CornerRadius" Value="5"/>
<Setter Property="BorderThickness" Value="5"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsActiveHighlight, RelativeSource={RelativeSource Mode=Self}}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="EnabledPulse" Storyboard="{DynamicResource PulseAccent}"/>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="EnabledPulse"/>
<BeginStoryboard x:Name="EnabledClear" Storyboard="{DynamicResource ClearAccent}"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
MainWindow.xaml
<local:HighlightBorder x:Name="Grouped_Controls">
<TextBlock> Example Content </TextBlock>
</local:HighlightBorder>
MainWindow.cs
Grouped_Controls.IsActiveHighlight = false; // Or true

Populate WPF combo box items in XAML style based on the state of another control

I think I'm close, but I'm missing something here.
I'm really trying to stretch my XAML knowledge and unclutter my code behind in my WPF projects.
I have a situation where I'd like to populate the items of a combobox based on the state of another control.
I have a style where I'm checking the value of a checkbox:
<Style
x:Key="{x:Type ComboBox}"
TargetType="{x:Type ComboBox}">
<Style.Triggers>
<DataTrigger
Binding="{Binding ElementName=SomeCheckBox, Path=IsChecked}"
Value="True">
Which seems to allow me to trigger on the value of the box.
I'm trying to figure out how to set up my setter now.
It seems that there is no way to access the items property ...
Here is what I've been messing with:
<Setter
Property="Items">
<Setter.Value>
<ComboBoxItem Content="SomeValue" />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
but it's pretty clear that isn't going to work.
Is there a way to do this using the setter?
Do I need to come up with some sort of list or lists that I stow in the window resources and use as an item source?
And how would I make that list?
based on the state of another control
I would do it in the code behind on the VM after binding the check box IsChecked to a property while also binding the ComboBox's to a list which changes based on the value of IsChecked.
Here is a quick example:
public bool IsOperationChecked // Bound to the checkbox
{
get { return _IsOperationChecked; }
set {
_IsOperationChecked= value;
OnPropertyChanged("IsOperationChecked");
Names = (value) ? new List<string>() {"alpha", "beta"} :
new List<string>() {"Bill", "Frank"};
}
}
public List<string> Names // Bound to the Combobox
{
get { return _Names; }
set { _Names = value; OnPropertyChanged("Names"); }
}
Use an array declared in your Resources:
<Window.Resources>
<x:Array Type="sys:String"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
x:Key="MyArray">
<sys:String>One</sys:String>
<sys:String>Two</sys:String>
</x:Array>
</Window.Resources>
...
<Style x:Key="{x:Type ComboBox}"
TargetType="{x:Type ComboBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=SomeCheckBox, Path=IsChecked}" Value="True">
<Setter Property="ItemsSource" Value="{StaticResource MyArray}" />
</DataTrigger>
</Style.Triggers>
</Style>

How to make a WPF resource be recalculated when a trigger's run?

In short: I've got a Style. It uses TemplateBinding a fair bit to make it parametrized instead of repeating myself over and over again. However, when a trigger for that style gets used and a resource gets used in a setter in that trigger, it just doesn't show up! Not even the default value gets shown. Here's a small program that replicates this issue:
TestDictionary.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:MyNamespace">
<Style TargetType="Button" x:Key="BtnTest">
<Style.Resources>
<Label Content="{TemplateBinding lcl:TestClass.String}" x:Key="innerLabel"/>
</Style.Resources>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Content" Value="{DynamicResource innerLabel}"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
MainWindow.xaml
<Window x:Class="MyNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:lcl="clr-namespace:MyNamespace"
Title="Test" Width="500" Height="350">
<Window.Resources>
<ResourceDictionary Source="TestDictionary.xaml"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Content="Enable/Disable" Click="Click"/>
<Button Grid.Column="1" x:Name="btn" Style="{DynamicResource BtnTest}" lcl:TestClass.String="TESTING"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
namespace MyNamespace
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Click(object sender, RoutedEventArgs e)
{
btn.IsEnabled = !btn.IsEnabled;
}
}
public class TestClass
{
public static string GetString(DependencyObject obj)
{
return (string)obj.GetValue(StringProperty);
}
public static void SetString(DependencyObject obj, string value)
{
obj.SetValue(StringProperty, value);
}
public static readonly DependencyProperty StringProperty =
DependencyProperty.RegisterAttached("String", typeof(string), typeof(TestClass), new PropertyMetadata("Default!"));
}
}
Instead of using a TemplateBinding, I also tried this:
{Binding Path=lcl:TestClass.String, RelativeSource={RelativeSource AncestorType={x:Type Button}}}
It still didn't work.
I know I'm probably doing something wrong, but the question is: what is it?
All you really need to make this work is to use RelativeSource in your binding. Since you are setting the attached property on the Button, in your style trigger, you can just bind to the attached property on self:
<Style TargetType="Button" x:Key="BtnTest">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Content"
Value="{Binding Path=(lcl:TestClass.String), RelativeSource={RelativeSource Self}}"/>
</Trigger>
</Style.Triggers>
</Style>
One cool thing about using your approach, since Button is a ContentControl, you're attached property can be any object, not just strings.
And to clarify what went wrong in your previous approach -
As others have said, TemplateBinding only works in ControlTemplates. It also only works when the DependencyProperty is defined on the class you are creating the template for (so you can never do a TemplateBinding to Grid.Row for example)
When binding to an attached property, the whole thing needs to be in parentheses, otherwise WPF will try to bind to a property of a property. Otherwise your RelativeSource binding was close!
I think if you want to have a Label inside the Button as the content, it may work (I didn't test that), but it doesn't seem like the best idea, as your Button can host any object you want.
EDIT for more complex example
So, if you need to display more than one dynamic property, I would recommend using a DataTemplate:
<Style TargetType="Button" x:Key="BtnTest">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Label Content="{Binding Path=(lcl:TestClass.String), RelativeSource={RelativeSource AncestorType={x:Type Button}}}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
Also, I want to point out that a DataTemplateSelector might be more applicable if you have multiple different criteria for changing the look of the content.
Now I see the details. What you should write before relative source is like:
Binding Path=(lcl:TestClass.String)
Do not forget to add parenthesis.
Your example does not work because TemplateBinding only works in a ControlTemplate. To achieve something akin to a TemplateBinding in Resources you need to do other stuff. Here's an example.
In order for TemplateBinding to work, you need to fix the code a little bit (this is just an example with no resources):
<Style x:Key="BtnTest" TargetType="{x:Type Button}">
<Setter Property="MinHeight" Value="100" />
<Setter Property="MinWidth" Value="200" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderBrush" Value="Blue" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2" Background="{TemplateBinding Background}">
<ContentPresenter RecognizesAccessKey="True" Content="{TemplateBinding lcl:TestClass.String}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</Style.Triggers>
</Style>
Useful links about this topic: Here, and here too.
EDIT:
You can also use the application settings instead of TestClass. Open "Project -> Properties: MyNamespace... -> Settings" and add your settings:
Name--------Type--------Scope--------Value
LabelText---string--------User----------Default
Set the your value for the LabelText in code. For example:
public MainWindow()
{
InitializeComponent();
MyNamespace.Properties.Settings.Default.LabelText = "Testing";
}
And use this ResourceDictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:properties="clr-namespace:MyNamespace.Properties"
xmlns:lcl="clr-namespace:MyNamespace">
<Style TargetType="Button" x:Key="BtnTest">
<Style.Resources>
<Label x:Key="innerLabel" Content="{Binding Source={x:Static properties:Settings.Default}, Path=LabelText, Mode=TwoWay}" />
</Style.Resources>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Content" Value="{DynamicResource innerLabel}"/>
</Trigger>
</Style.Triggers>
</Style>

How to give my custom control an overridable default margin?

Problem
I've created a custom control (OmniBox), which has its base style set with:
<Style x:Key="GridStyle" TargetType="Grid" BasedOn="{StaticResource BaseElement}">
<Setter Property="Margin" Value="0,2" />
</Style>
But when I'm using my control, I want to be able to do something like:
<UserControl.Resources>
<Style TargetType="{x:Type ui:OmniBox}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="0,10"/> <!--Not Working?-->
</Style>
</UserControl.Resources>
<Grid>
<StackPanel>
<ui:OmniBox x:Name="One"... />
<ui:OmniBox x:Name="Two"... />
...
And have all instances of my control take on that default margin. Unfortunately, my controls are not responding to the style set in the resources. They are just keeping their default margin of "0,2".
Strangely, if I explicitly set the margin on my controls like so:
<ui:OmniBox x:Name="One" Margin="0,10" Style="OBDefaultStyle" ... />
<ui:OmniBox x:Name="Two" Margin="0,10" ... />
...
They DO use the margin of "0,10" rather than "0,2". How come the template type isn't working?
If it's relevant, my OmniBox control templates all look like this:
<Style TargetType="{x:Type local:OmniBox}" x:Key="OBDefaultStyle">
<Setter Property="Template" Value="{StaticResource OBDefaultTemplate}" />
</Style>
<ControlTemplate TargetType="{x:Type local:OmniBox}" x:Key="OBDefaultTemplate">
<Grid x:Name="PART_Grid" Style="{StaticResource GridStyle}">
... (Content)
</Grid>
</ControlTemplate>
First Attempt
In my grid style, I've tried setting Margin to
<Setter Property="Margin"
Value="{Binding Path=Margin, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:OmniBox}}}" />
But it didn't help in sucking down the templated margin.
Second Attempt
I tried creating a custom margin dependency property and binding the grid to that:
<Style x:Key="GridStyle" TargetType="Grid" BasedOn="{StaticResource BaseElement}">
<Setter Property="Margin" Value="{Binding Path=MyMargin, RelativeSource={RelativeSource TemplatedParent}}" />
</Style>
My custom property was defined as:
public static readonly DependencyProperty MarginProperty = DependencyProperty.Register("Margin", typeof(Thickness), typeof(OmniBox), new FrameworkPropertyMetadata(new Thickness(0,2,0,2), new PropertyChangedCallback(OnMarginChanged)));
Anyways it didn't work. The default margin set in the dependency property above is still overriding the margin I'm trying to set in the style template.
You can add a default style for a custom control by overriding the metadata for the DefaultStyleKey:
public class MyButton : Button
{
static MyButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyButton), new FrameworkPropertyMetadata(typeof(MyButton)));
}
}
You then create a resource dictionary called Generic.xaml that is located in a directory called Themes in the root of the project (so the path will be "/Themes/Generic.xaml"). In that resource dictionary you create a default style for your control:
<!-- Base the style on the default style of the base class, if you don't want to completely
replace that style. If you do, remember to specify a new control template in your style as well -->
<Style TargetType="SomeNamespace:MyButton" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Margin" Value="10" />
</Style>
If you just add a MyButton control it will get the default style, but you can override properties set in the default style by applying a new style:
<Window x:Class="SomeNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:SomeNamespace="clr-namespace:SomeNamespace"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="SomeNamespace:MyButton">
<Setter Property="Margin" Value="20" />
</Style>
</Window.Resources>
<Grid>
<SomeNamespace:MyButton />
</Grid>
</Window>
GridStyle specifies TargetType="Grid", so the setter <Setter Property="Margin" Value="0,2" /> applies to the Grid at the root of the control template. Setting the Margin property of the containing OmniBox has no effect of the margin of that grid.
Try specifying this in the template:
<Grid x:Name="PART_Grid" Margin="{TemplateBinding Margin}">
Notice I did not set the Style property as you did in the template. This is because the grid's Margin property will always reflect the Margin property of the OmniBox containing it, negating the effect of the Margin property in GridStyle. Instead you will want to default the OmniBox.Margin property and remove GridStyle entirely:
<Style TargetType="{x:Type local:OmniBox}" x:Key="OBDefaultStyle">
<Setter Property="Margin" Value="0 2" />
<Setter Property="Template" Value="{StaticResource OBDefaultTemplate}" />
</Style>
Have you overridden the DefaultStyleKey property in your OmniBox control?
After happening on this question, I figured out what I needed to do. In the control's class, I need to override the margin property's default value:
static OmniBox()
{
MarginProperty.OverrideMetadata(typeof(OmniBox), new FrameworkPropertyMetadata(new Thickness(0,2,0,2)));
}
After that, I get rid of the margin on the "Grid" component of the omnibox completely, since the control itself carries a margin. Now when the user sets the "Margin" property on the OmniBox, it accepts it, if they don't, it uses the default value.
Thank you all so much for your suggestions and effort.

Categories

Resources