I want to ask about the right way if I want to create Bindable user control consisting of two controls. I am not sure about what I am doing - whether I do it correctly , because I run into some problems.
Here is what I am trying to do:
Lets call this control ucFlagControl . Create new , custom user control ... Its purpose is to show Color interpretation of logic ( True/ False ) value in variable , type of Bool.
What I used to do before was that I use Rectangle
, and Bind FillProperty
to bool
ean value using Converter
What I did to make it works was , that I made a usercontrol , and put rectangle and label inside than I added this code:
public partial class ucStatusFlag : UserControl
{
public ucStatusFlag()
{
InitializeComponent();
}
public string LabelContent
{
get { return (string)GetValue(LabelContentProperty); }
set
{
SetValue(LabelContentProperty, value);
OnPropertyChanged("LabelContent");
}
}
///in case that I use integer or array
public int BitIndex
{
get { return (int)GetValue(BitIndexProperty); }
set
{
SetValue(BitIndexProperty, value);
OnPropertyChanged("BitIndex");
}
}
public string BindingSource
{
get { return (string)GetValue(BindingSourceProperty); }
set
{
SetValue(BindingSourceProperty, value);
OnPropertyChanged("BindingSource");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
/// <summary>
/// Identified the Label dependency property
/// </summary>
public static readonly DependencyProperty LabelContentProperty =
DependencyProperty.Register("LabelContent", typeof(string), typeof(ucStatusFlag), new PropertyMetadata("LabelContent"));
public static readonly DependencyProperty BitIndexProperty =
DependencyProperty.Register("BitIndex", typeof(int), typeof(ucStatusFlag), new PropertyMetadata(0));
public static readonly DependencyProperty BindingSourceProperty =
DependencyProperty.Register("(BindingSource", typeof(string), typeof(ucStatusFlag), new PropertyMetadata(""));
private void StatusFlag_Loaded(object sender, RoutedEventArgs e)
{
if (BindingSource.Length > 0)
{
Binding bind = new Binding();
string s = LabelContent;
int i = BitIndex;
bind.Converter = new StatusToColor();
bind.Path = new PropertyPath(BindingSource);
bind.ConverterParameter = BitIndex.ToString();
bind.Mode = BindingMode.OneWay;
bind.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
recStatusBit.SetBinding(Rectangle.FillProperty, bind);
}
}
private class StatusToColor : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
byte bDataWordIdx;
byte bDataBitIdx;
Byte.TryParse((string)parameter, out bDataBitIdx);
if (Object.ReferenceEquals(typeof(UInt16[]), value.GetType()))
{
UInt16[] uiaData = (UInt16[])value;
bDataWordIdx = (byte)uiaData[0];
if ((uiaData[bDataBitIdx / 16] >> (bDataBitIdx % 16) & 0x1) == 1)
{
return Brushes.Green;
}
else
{
return Brushes.Red;
}
}
else if (Object.ReferenceEquals(typeof(UInt16), value.GetType()))
{
UInt16 uiaData = (UInt16)value;
if (((uiaData >> bDataBitIdx) & 0x1) == 1)
{
return Brushes.Green;
}
else
{
return Brushes.Red;
}
}
return 0;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return 0;
}
}
}
}
Than I realized that I can easily bind content and I do not have to create public static readonly DependencyProperty LabelContentProperty
but just property
public new string Content
{
get { return (string)label.Content; }
set
{
SetValue(label.Content, value);
OnPropertyChanged("Content");
}
}
this overrides the original content so I am able to Bind and/or assign the text of the label in upper level - in e.g. MainWindow.xaml where this user control is put
First question is if this is in this case OK or if there is some background I am not aware of and I should even such small controls do in different way - I would like to make dll. from it an load it to toolbox - I tested it works. And than use it in for example stack panel .
Second question is that I have problem with a rectangle "Fill"
property . I am not able to bind that property like I bind content .
I know that the rectangle is derived from Shape class so I am not sure if it has something to do with this.
If I am able to do the inner binding or connection same as in
Content
I can remove the converters than and just bind it in e.g. MainWindow.xaml file (using the converter and converter parameter )
But FillProperty
does not work for me so I am not sure about my point of view .
Thank you for suggestions
EDIT:
well I am sorry but I did not catch all you want to say in a comment below. Could you please explain closer ? I know that the code above is not the right way to do it ... ? Or can you post any article about it ? my actual code is like this: In a user control ... I removed all the code from code behind ...
' <Label x:Name="lStatusBit" Grid.Column="1" Padding="0" VerticalContentAlignment="Center" Margin="2,1,17,2" />
<Rectangle x:Name="recStatusBit" Margin="0,3,1,7" />'
Content property works, I cant see Rectangle , and rectangle fill property ... Other problem is if I fill in Content property in XAML where my uc is placed , Rectangle disappears .
I know I'm a year late to the party, but I'll answer incase anyone else comes across this.
You should use a TextBlock
control instead of Label
controls if you want to display pure text. Labels have a content element which is re-rendered/computed many more times than a TextBlock's simple Text
property.
You should avoid using magic strings, e.g. "LabelContent"
. You should use the C# nameof()
expression when referencing property names. For example:
I use lambda expressions to clean up the code a bit, but this is just preference.
public string LabelContent
{
get => (string)GetValue(LabelContentProperty);
set => SetValue(LabelContentProperty, value);
}
public static readonly DependencyProperty LabelContentProperty =
DependencyProperty.Register(
nameof(LabelContent),
typeof(string),
typeof(ucStatusFlag),
new PropertyMetadata("Default Value"));
This will prevent runtime errors due to mistyped text, will allow you to jump to the property's reference, will make refactoring easier, and will make debugging easier by giving you a compile error that's easy to find (if the property doesn't exist).
DataTrigger Example
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<!-- The default value -->
<Setter Property="Background" Value="Transparent" />
<!-- Your trigger -->
<Style.Triggers>
<DataTrigger Binding="{Binding SomeBooleanValue}" Value="True">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
A DataTrigger is a quick and easy way to style a control by binding to a property on your ViewModel (assuming you're using the MVVM structure), but there are some cons - like reusing the same style on a different View whose ViewModel's properties are different. You'd have to rewrite the entire styling again.
Lets turn it into a reusable control where we can (1) specify a highlight background color, and (2) use a boolean to determine whether the control is highlighted.
I make my templated controls in a separate C# class file and put the control's styling in another separate resource dictionary file instead of using a UserControl.
Here's what a few of my controls look like in the solution explorer: Solution Files Snippet
It sounds like you want to create a reusable templated control.
Here are the basic steps:
<Style TargetType="{x:Type local:Example}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Example}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<!-- Control template styles -->
<ResourceDictionary Source="pack://application:,,,/Themes/ExpansionPanel.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/NavButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/TextDocument.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/TextDocumentToolBar.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/TextEditor.xaml" />
<ResourceDictionary Source="pack://application:,,,/Themes/HighlightTextBlock.xaml" />
<!-- etc... -->
</ResourceDictionary.MergedDictionaries>
<!-- Other styles or whatever -->
</ResourceDictionary>
<Application>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!-- Other resource dictionaries... -->
<ResourceDictionary Source="pack://application:,,,/Themes/Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
<!-- Other resource dictionaries... -->
</ResourceDictionary>
</Application.Resources>
</Application>
Your control's .cs file would resemble something similar to...
public class HighlightTextBlock : Control
{
#region Private Properties
// The default brush color to resort back to
public Brush DefaultBackground;
#endregion
static HighlightTextBlock()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(HighlightTextBlock), new FrameworkPropertyMetadata(typeof(HighlightTextBlock)));
}
// Get the default background color and set it.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
DefaultBackground = Background;
}
#region Dependency Properties
/// <summary>
/// The text to display.
/// </summary>
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
nameof(Text), typeof(string), typeof(HighlightTextBlock), new PropertyMetadata(string.Empty));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
/// <summary>
/// Whether or not the background should be highlighted.
/// </summary>
// This uses a callback to update the background color whenever the value changes
public static readonly DependencyProperty HighlightProperty = DependencyProperty.Register(
nameof(Highlight), typeof(bool),
typeof(HighlightTextBlock), new PropertyMetadata(false, HighlightPropertyChangedCallback));
public bool Highlight
{
get => (bool)GetValue(HighlightProperty);
set => SetValue(HighlightProperty, value);
}
/// <summary>
/// The highlight background color when <see cref="Highlight"/> is true.
/// </summary>
public static readonly DependencyProperty HighlightColorProperty = DependencyProperty.Register(
nameof(HighlightColor), typeof(Brush),
typeof(HighlightTextBlock), new PropertyMetadata(null));
public Brush HighlightColor
{
get => (Brush)GetValue(HighlightColorProperty);
set => SetValue(HighlightColorProperty, value);
}
#endregion
#region Callbacks
// This is the callback that will update the background
private static void HighlightPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var target = (HighlightTextBlock)dependencyObject;
if (target.Highlight)
target.Background = target.HighlightColor;
else
target.Background = target.DefaultBackground;
}
#endregion
}
Your .xaml file would look something like...
<Style x:Key="HighlightTextBlock" TargetType="{x:Type ctrl:HighlightTextBlock}">
<!-- Default setters... -->
<!-- Define your control's design template -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ctrl:HighlightTextBlock}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<!--
I only bound the Text and Background property in this example
Make sure to bind other properties too.. like Visibility, IsEnabled, etc..
-->
<TextBlock Text="{TemplateBinding Text}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--
Set the default style for the control
The above style has a key, so controls won't use that style
unless the style is explicitly set.
e.g.
<ctrl:HighlightTextBlock Style={StaticResource HighlightTextBlock} />
The reason I used a key above is to allow extending/reusing that default style.
If a key wasn't present then you wouldn't be able to reference it in
another style.
-->
<Style TargetType="{x:Type ctrl:HighlightTextBlock}" BasedOn="{StaticResource HighlightTextBlock}" />
Add a reference to the control's resource dictionary in Generic.xaml, like in step 2's code snippet.
Usage:
I'm binding the IsChecked
property to a IsHighlighted
property on my ViewModel.
You can bind it to whatever.
<StackPanel>
<ToggleButton IsChecked="{Binding IsHighlighted}" Content="{Binding IsHighlighted}"
Width="100" Height="35" Margin="5"/>
<ctrl:HighlightTextBlock Background="Transparent" HighlightColor="Red"
Text="HELLO WORLD!!!" Highlight="{Binding IsHighlighted}"
Width="100" Height="35" HorizontalAlignment="Center" />
</StackPanel>