WPF Style - Use DependencyProperty as a CONST value - c#

I'm constructing my own Button with icons and text, passing as DependencyProperty colors, icon path and text. No problem so far. Here's the code for the one of the color property.
//Text Color
public static readonly DependencyProperty forColorProperty =
DependencyProperty.Register(
"forColor",
typeof(System.Windows.Media.SolidColorBrush),
typeof(AppButtonPro),
new PropertyMetadata(Colors.DarkBlue));
public System.Windows.Media.SolidColorBrush forColor
{
get { return (System.Windows.Media.SolidColorBrush)GetValue(forColorProperty);}
set { SetValue(forColorProperty, value); }
}
And the XAML code:
<ap:AppButtonPro Style="{DynamicResource AppButtonFast}"
forColor="{Binding Source={x:Static const:Colors.White}}"
backColor="{Binding Source={x:Static const:Colors.LightBlue}}"
Grid.Column="0"
Grid.Row="1"
Grid.RowSpan="1"
Grid.ColumnSpan="3"
x:Name="btScrollUp"
Click="btScrollUp_Click"
textContentString="Text"
iconContentString="pack://application:,,,/DllData;component/Appearence/down.png"
>
</ap:AppButtonPro>
I'd like to swap colors at click to enhance contrast, so I'd like to assign the foreground color to the background and viceversa. I assume I can't simply invert the binding, so I'd like to store the color values somewhere in the style and use them as local variables to play in the states.
For example here's a textblock with foreground color with binding to forColor value as declared in XAML: I'd like to change it to the value of backColor in some visualstate and the change it back to normal when needed.
<TextBlock x:Name="textContent"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Text="{Binding textContentString, RelativeSource={RelativeSource TemplatedParent}}"
Foreground="{Binding forColor, RelativeSource={RelativeSource TemplatedParent}}"/>

Related

C# WPF: Insert Shape Into TextBlock

I am looking to insert a coloured square (with a border) into a TextBlock in WPF. The colour of the square needs to be set dynamically, and so ideally this should happen in the code-behind, not XAML.
I'm guessing the best way to do this is with a InlineUIContainer, but I can't work out how to position a Rectangle such that it aligns with the text, and is sized appropriately to the font size.
So far I have:
Color myColor = GetMyColor();
TextBlock textBlock = new TextBlock();
textBlock.Inlines.Add(new Run("My color: "));
// Attempt with a Canvas and Rectangle
Canvas canvas = new Canvas();
canvas.Children.Add(new Rectangle() { Height = 6, Width = 6, Fill = new SolidColorBrush(color.Value) });
textBlock.Inlines.Add(new InlineUIContainer(canvas));
// Hacky version that looks terrible
textBlock.Inlines.Add(new Run(" ") { Background = new SolidColorBrush(myColor) });
The problem here is that the Rectangle is created from the text baseline, hanging down. I would like it to be vertically centred relative to the text, square (i.e. aspect ratio of 1), and ideally automatically sized to the font size.
I'd wondered if a Viewbox was somehow useful, or some combination of VerticalAlignment properties, but I couldn't make them work. Any suggestions would be greatly appreciated.
Depending on the size of square you want, you could try using unicode characters 0x25A0 or 0x25AA.
Here's an example defined in Xaml, but you could achieve the same effect in code behind too.
<TextBlock FontFamily="Segoe UI">
<Run Text="ABC" />
<Run Foreground="Red" Text="■" />
<Run Foreground="Green" Text="▪" />
</TextBlock>
<TextBlock FontFamily="Tahoma">
<Run Text="ABC" />
<Run Foreground="Red" Text="■" />
<Run Foreground="Green" Text="▪" />
</TextBlock>
Note that different font families render these characters with different proportion compared to the hight of the regular letters.
You can use a ContentControl and a DataTemplate.
A UserControl or custom Control or ContentControl is also a good solution, especially if you like to add a behavior.
The following example uses a ContentControl and a DataTemplate to display a centered Rectangle next to a text, where the shape's color and the text are dynamic values. The size of the shape is relative to the FontSize applied to the ContentControl.
The final size of the shape can be adjusted by setting a Margin on the Viewbox or by attaching a IValueConverter to the Height binding of the Viewbox:
MainWindow.xaml
<Window>
<Window.Resources>
<DataTemplate x:Key="DataModelTemplate"
DataType="{x:Type DataModel}">
<DockPanel HorizontalAlignment="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=HorizontalContentAlignment}">
<TextBlock x:Name="TextLabel"
FontSize="{Binding RelativeSource={RelativeSource AncestorType=ContentControl}, Path=FontSize}"
Text="{Binding TextValue}"
VerticalAlignment="Center" />
<Viewbox Height="{Binding ElementName=TextLabel, Path=ActualHeight}"
Margin="8"
Stretch="Uniform">
<Rectangle Width="10"
Height="10"
Fill="{Binding Color}" />
</Viewbox>
</DockPanel>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl x:Name="TextControl1"
ContentTemplate="{StaticResource DataModelTemplate}"
FontSize="50" />
<ContentControl x:Name="TextControl2"
ContentTemplate="{StaticResource DataModelTemplate}"
FontSize="20" />
</StackPanel>
</Window>
MainWindow.xaml.cs
partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.TextControl1.Content = new DataModel("#Test 1", Brushes.Yellow);
this.TextControl2.Content = new DataModel("#Test 2", Brushes.Red);
}
}
DataModel.cs
class DataModel : INotifyPropertyChanged
{
public DataModel(string textValue, Brush color)
{
this.TextValue = textValue;
this.Color = color;
}
public string TextValue { get; }
public Brush Color { get; }
}

WPF TextAlignmentProperty.OverrideMetadata not working in inheritor of TextBox

I have created a custom control, which inherits TextBox. It basically has an extra property 'Seconds' and set a binding on 'Text', to shown the 'Seconds' formatted, as eg. 2m 5s, using a converter.
I now want to default right-align the text.
From other custom controls I know we sometimes will want to set/override values using styles. If I set the value directly in the constructor I will not be able to do this.
I would usually something like this:
TextAlignmentProperty.OverrideMetadata(typeof(DurationTextBox), new FrameworkPropertyMetadata(TextAlignment.Right));
But this does NOT appear to work:
First two have the default alignment, then they are Left, Center and Right aligned directly on the controls. Seconds row has a style setting alignment to Center
I have bound a TextBlock to the TextAlignment of the first DurationTextBox, and this states that the aligment is 'Right', but this is not how it is shown!
Can anyone explain:
A. Why this is not working?
B. How to do this correctly, or something with the same end effect? (default aligned Right, but possible to override from Style)
C# class :
Please note that this is a simplified version. The complete one has Min, Max, option of confirming value changed and option for out of range action, which is the reason for the structure of the class. Please keep focus on the TextAlignment issue!
(The SecondsToDurationStringConverter and DurationStringValidator can be removed to make the example compile with the same effect)
public class DurationTextBox : TextBox
{
#region Dependency properties
/// <summary>
/// Property for <see cref="Seconds"/>
/// </summary>
[NotNull] public static readonly DependencyProperty SecondsProperty = DependencyProperty.Register(nameof(Seconds), typeof(double), typeof(Demo.DurationTextBox), new FrameworkPropertyMetadata(default(double), SecondsChangedCallback) { BindsTwoWayByDefault = true });
/// <summary>
/// Seconds to show as duration string
/// </summary>
public double Seconds
{
// ReSharper disable once PossibleNullReferenceException
get { return (double)GetValue(SecondsProperty); }
set { SetValue(SecondsProperty, value); }
}
/// <summary>
/// Property for <see cref="EditValue"/>
/// </summary>
[NotNull] public static readonly DependencyProperty EditValueProperty = DependencyProperty.Register(nameof(EditValue), typeof(double), typeof(Demo.DurationTextBox), new FrameworkPropertyMetadata(default(double), EditValueChangedCallback) { BindsTwoWayByDefault = true });
/// <summary>
/// Number being edited by the actual text box. Transferred to <see cref="Seconds"/>.
/// <para>Do NOT bind to this property from outside this control</para>
/// </summary>
public double EditValue
{
// ReSharper disable once PossibleNullReferenceException
get { return (double)GetValue(EditValueProperty); }
set { SetValue(EditValueProperty, value); }
}
#endregion Dependency properties
private bool _isLocked;
static DurationTextBox()
{
// TextAlignment
TextAlignmentProperty.OverrideMetadata(typeof(Demo.DurationTextBox), new FrameworkPropertyMetadata(TextAlignment.Right));
}
/// <inheritdoc />
public DurationTextBox()
{
SecondsToDurationStringConverter secondsToDurationStringConverter = new SecondsToDurationStringConverter();
// Text
Binding binding = new Binding(nameof(EditValue)) { Source = this, Converter = secondsToDurationStringConverter, NotifyOnValidationError = true };
binding.ValidationRules.Add(new DurationStringValidation());
SetBinding(TextProperty, binding);
}
private static void SecondsChangedCallback([CanBeNull] DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Demo.DurationTextBox durationTextBox = d as Demo.DurationTextBox;
if (durationTextBox == null) return;
if (!durationTextBox._isLocked)
{
durationTextBox._isLocked = true;
durationTextBox.SetCurrentValue(EditValueProperty, durationTextBox.Seconds);
durationTextBox._isLocked = false;
}
}
private static void EditValueChangedCallback([CanBeNull] DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Demo.DurationTextBox durationTextBox = d as Demo.DurationTextBox;
if (durationTextBox == null) return;
if (!durationTextBox._isLocked)
{
durationTextBox._isLocked = true;
durationTextBox.SetCurrentValue(SecondsProperty, durationTextBox.EditValue);
durationTextBox._isLocked = false;
}
}
}
XAML code:
<Label Content="Demo.DurationTextBox" FontWeight="Bold"/>
<WrapPanel>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" x:Name="DemoDurationTextBox"/>
<TextBlock Text="{Binding ElementName=DemoDurationTextBox, Path=TextAlignment}"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Left"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Center"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Right"/>
</WrapPanel>
<WrapPanel>
<WrapPanel.Resources>
<Style TargetType="demo:DurationTextBox">
<Setter Property="TextAlignment" Value="Center"/>
</Style>
</WrapPanel.Resources>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Left" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Center" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Right" />
</WrapPanel>
A: This isn't working because TextBox.TextAlignment is inherited, so by default the active value is determined by the parent control at runtime. It doesn't make sense for a class to override the default value of the static DependencyProperty object itself.
The fact that the debug binding you added displays "Right" is strange, and probably an internal WPF optimisation glitch.
B: The correct solution is to set TextBox.TextAlignment="Right" (note the type name qualifier) on a parent control, e.g. your wrap panel. That value will then be automatically applied to all child text blocks unless they or an intermediate parent override it further, including via a style.
C: I would add that the code you posted seems to be an attempt to re-invent DataGrid. That control supports transactional editing out of the box and might save you a lot of time if you switch to it!
While it is not the solution I would have preferred, I have found a way that allows me to have the default alignment of my control to Right, while being able to overwrite it locally by using a style, or directly on the control, without affecting other TextBoxes
I made a default style:
<Style TargetType="controls:DurationTextBox">
<Setter Property="TextAlignment" Value="Right"/>
</Style>
As I have other resource dictionaries which must be included anyway this will work in my situation.
I would have preferred simply setting the default value of my control, but according to Artfunkel that is sadly not possible.
The issue with my chosen approach of using a default style is if a different default style is set for TextBox in a project, the DurationTextBox will not use/inherit this style, because it has its own default style, thus someone using the library has to also set a similar style/override the style for the DurationTextBox for them not to appear different.
If the DurationTextBox had not needed a different default style, containing the text alignment, it would have been possible to have the default style be the same as a TextBox, but this is not a possibility now.
As we do have a different default style for TextBox I have added a BasedOn to my default style:
<Style TargetType="controls:DurationTextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="TextAlignment" Value="Right"/>
</Style>

How can I have a ControlTemplate that only creates a container for the default, unaltered visual tree of a control?

I'm trying to figure out how to change a control's template to something that will make it held inside a Grid, like this:
<ControlTemplate x:Key="containedTemplate">
<Grid>
<!-- place templated control here -->
</Grid>
</ControlTemplate>
I of course want any of the inner control's properties to be synced automatically with the templated control.
Can this be done at all?
Here's an hypothetical example for a TextBox template:
<ControlTemplate x:Key="textTemplate" TargetType="{x:Type TextBox}">
<Grid Background="Red">
<TextBox Name="InnerTextBox" Margin="5,5,5,5"/>
</Grid>
</ControlTemplate>
Now if I did apply the template on a TextBox instance like this:
<TextBox Text="{Binding MyTextProperty}" Template="{StaticResource textTemplate}"/>
... then the control would magically be a Grid, containing a TextBox with a few margins and whose Text's property would be bound to MyTextProperty of whatever DataContext instance has been set:
<!-- runtime visual tree I'd like to be produced by the above XAML -->
<Grid Background="Red">
<TextBox Text="{Binding MyTextProperty}" Margin="5,5,5,5"/>
</Grid>
If I had the following code:
<StackPanel>
<TextBox Text="{Binding MyTextProperty}" Template="{StaticResource textTemplate}"/>
<TextBox Text="{Binding MyOtherTextProperty}" Template="{StaticResource textTemplate}"/>
<TextBox Text="{Binding YetAnotherTextProperty}" Template="{StaticResource textTemplate}"/>
</StackPanel>
The resulting tree would be this:
<!-- runtime visual tree I'd like to be produced by the above XAML -->
<StackPanel>
<Grid Background="Red">
<TextBox Text="{Binding MyTextProperty}" Margin="5,5,5,5"/>
</Grid>
<Grid Background="Red">
<TextBox Text="{Binding MyOtherTextProperty}" Margin="5,5,5,5"/>
</Grid>
<Grid Background="Red">
<TextBox Text="{Binding YetAnotherTextProperty}" Margin="5,5,5,5"/>
</Grid>
</StackPanel>
In these examples you can see that the TextBox's Text property is correctly propagated down to the "inner" TextBox instance. The control's default visual tree is also preserved (borders, typing area, etc.).
I'm aware of template parts but as I said I'm trying to find a global approach here, and I DO NOT want to change the control's appearance; only put it inside a container.
frankly, this question exhausted me, i have this only answer but not convince me a lot.
first you should create multi ControlTemplates for each control that you want to set your template then create this class
public class ControlTemplateConverter
{
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(ControlTemplateConverter), new UIPropertyMetadata(false, IsEnabledChanged));
private static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ControlTemplate t;
if (d == null) return;
if (d is TextBlock)
t = App.Current.FindResource("TextBoxTemplate") as ControlTemplate;
else if (d is CheckBox)
t = App.Current.FindResource("CheckBoxTemplate") as ControlTemplate;
// and So On
(d as Control).Template = t;
}
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
}
and your control should like this:
<TextBox local:ControlTemplateConverter.IsEnabled="True"></TextBox>
<CheckBox local:ControlTemplateConverter.IsEnabled="True"></CheckBox>

Silverlight TargetNullValue binding for Brush dependency property

I have a reusable user control with a dependency property which sets the color of a rectangle. The property uses Brush as type.
The data binding works fine, however I would like to add a fallback and null value when the binding has errors or its value is not specified.
Here is my XAML:
<Rectangle Fill="{Binding Path=UnderLineColor, ElementName=Header,
FallbackValue=LightGrey, TargetNullValue=LightGrey}"
Height="2"
Margin="0,2"
Grid.Row="1"
Grid.ColumnSpan="2" />
And the code of the UnderLineColor DP:
public Brush UnderLineColor
{
get { return (Brush)GetValue(UnderLineColorProperty); }
set { SetValue(UnderLineColorProperty, value); }
}
public static readonly DependencyProperty UnderLineColorProperty =
DependencyProperty.Register("UnderLineColor", typeof(Brush), typeof(SectionHeader), null);
The issue is that SL doesn't seem to accept the fallback and nullvalue I specify.
What value should I write into these properties to make it work? Or should I use a ValueConverter instead of this approach?
Edit:
Top tip for today: Grey != Gray. Issue is fixed now. :)

silverlight 2 binding data to transforms?

I am working on creating a tag cloud in Silverlight 2 and trying to bind data from a List collection to a Scale transform on a TextBlock. When running this I get an AG_E_PARSER_BAD_PROPERTY_VALUE error. Is it possible to data bind values to transforms in Silverlight 2? If not could I do something to the effect of FontSize={Binding Weight*18} to multiply the tag's weight by a base font size? I know this won't work, but what is the best way to calculate property values for items in a DataTemplate?
<DataTemplate>
<TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Stretch" TextWrapping="Wrap" d:IsStaticText="False" Text="{Binding Path=Text}" Foreground="#FF1151A8" FontSize="18" UseLayoutRounding="False" Margin="4,4,4,4" RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="{Binding Path=WeightPlusOne}" ScaleY="{Binding Path=WeightPlusOne}"/>
</TransformGroup>
</TextBlock.RenderTransform>
The issue seems to be Rule #1 from this post:
The target of data binding must be a FrameworkElement.
So since ScaleTransform isn't a FrameworkElement it doesn't support binding. I tried to bind to a SolidColorBrush to test this out and got the same error as with the ScaleTransform.
So in order to get around this you can create a control that exposes a dependency property of your tag data type. Then have a property changed event that binds the properties of your tag data to the properties in the control (one of which would be the scale transform). Here is the code I used to test this out.
items control:
<ItemsControl x:Name="items">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:TagControl TagData="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
tag control xaml:
<UserControl x:Class="SilverlightTesting.TagControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<TextBlock x:Name="text" TextWrapping="Wrap" FontSize="18" Margin="4,4,4,4">
<TextBlock.RenderTransform>
<ScaleTransform x:Name="scaleTx" />
</TextBlock.RenderTransform>
</TextBlock>
</UserControl>
tag control code:
public partial class TagControl : UserControl
{
public TagControl()
{
InitializeComponent();
}
public Tag TagData
{
get { return (Tag)GetValue(TagDataProperty); }
set { SetValue(TagDataProperty, value); }
}
// Using a DependencyProperty as the backing store for TagData. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TagDataProperty =
DependencyProperty.Register("TagData", typeof(Tag), typeof(TagControl), new PropertyMetadata(new PropertyChangedCallback(TagControl.OnTagDataPropertyChanged)));
public static void OnTagDataPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var tc = obj as TagControl;
if (tc != null) tc.UpdateTagData();
}
public void UpdateTagData()
{
text.Text = TagData.Title;
scaleTx.ScaleX = scaleTx.ScaleY = TagData.Weight;
this.InvalidateMeasure();
}
}
Seems like overkill for just setting a single property, but I couldn't find an easier way.

Categories

Resources