Change Class-Binded Shadow Colour WPF - c#

I was looking on how to replicate a Google button's shadow on hover effect in my WPF document and came across this: https://stackoverflow.com/a/53031057/12299798, which answers my problem exactly, but the only issue I have is that I normally do all the frontend things in XAML and so I am not quite experienced in the styling in C#. My problem is that I want to 'Bind' my foreground colour to the shadow colour, this is because in the code it only sets it to a greyish colour which would mean I would have to do a whole other class for each colour that I want to set it for. Here is my code now:
MainWindow.xaml:
<Button Content="shadow">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="local:UI.Elevation" Value="10"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="local:UI.Elevation" Value="0"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
UI.cs:
public static class UI
{
public static readonly DependencyProperty ElevationProperty = DependencyProperty.RegisterAttached("Elevation", typeof(double), typeof(UI), new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsRender, null, OnRedElevationChanged));
public static double GetElevation(this UIElement element) => element.GetValue(ElevationProperty) as double? ?? default;
public static void SetElevation(this UIElement element, double elevation) => element.SetValue(ElevationProperty, elevation);
private static object OnRedElevationChanged(DependencyObject d, object value)
{
if (d is UIElement element && value is double elevation)
if (elevation == 0)
element.Effect = null;
else
{
Effect e = CreateElevation(elevation, element.Effect);
if (e != null)
element.Effect = e;
}
return value;
}
private static Effect CreateElevation(double elevation, Effect source)
{
void MixShadows(DropShadowEffect nearest, DropShadowEffect matched, double balance)
{
matched.BlurRadius = matched.BlurRadius * (1 - balance) + nearest.BlurRadius * balance;
matched.ShadowDepth = matched.ShadowDepth * (1 - balance) + nearest.ShadowDepth * balance;
}
DropShadowEffect[] shadows = new DropShadowEffect[]
{
new DropShadowEffect()
{
BlurRadius = 5,
ShadowDepth = 1
},
new DropShadowEffect()
{
BlurRadius = 8,
ShadowDepth = 1.5
},
new DropShadowEffect()
{
BlurRadius = 14,
ShadowDepth = 4.5
},
new DropShadowEffect()
{
BlurRadius = 25,
ShadowDepth = 8
},
new DropShadowEffect()
{
BlurRadius = 35,
ShadowDepth = 13
}
};
elevation = Math.Max(0, elevation / 12 * shadows.Length - 1);
int prevIndex = (int)Math.Floor(elevation), index = (int)elevation, nextIndex = (int)Math.Ceiling(elevation);
double approx = elevation - index;
DropShadowEffect shadow = shadows[index];
if (approx != 0)
MixShadows(approx < 0 ? shadows[prevIndex] : shadows[nextIndex], shadow, Math.Abs(approx));
bool modify = false;
if (source is DropShadowEffect sourceShadow)
{
sourceShadow.BlurRadius = shadow.BlurRadius;
sourceShadow.ShadowDepth = shadow.ShadowDepth;
shadow = sourceShadow;
modify = true;
}
shadow.Direction = 270;
shadow.Color = Colors.Red;
shadow.Opacity = .42;
shadow.RenderingBias = RenderingBias.Performance;
return modify ? null : shadow;
}
}
And to get what I want in XAML I would do something like this:
<Button Content="shadow">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="local:UI.Elevation" Value="10"/>
<Setter Property="local:UI.Elevation.Foreground" Value="Blue"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="local:UI.Elevation" Value="0"/>
<Setter Property="local:UI.Elevation.Foreground" Value="Blue"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
But I don't quite know how to link up the local:UI.Elevation.Foreground to change the shadow colour, I would have to change this line here from UI.cs: shadow.Color = Colors.Red; and bind it to the Elevation.Foreground, but I am having difficulties. Can anyone help?

All you need to do is updated the UI class with a new attached property named something like Color of type Color and give it a default color you like.
Then, in its OnColorChanged handler, call OnElevationChanged so that the shadow gets recreated.
#region Color (Attached Property)
public static readonly DependencyProperty ColorProperty =
DependencyProperty.RegisterAttached(
"Color",
typeof(Color),
typeof(UI),
new PropertyMetadata(Colors.Black, OnColorChanged));
public static Color GetColor(DependencyObject obj)
{
return (Color)obj.GetValue(ColorProperty);
}
public static void SetColor(DependencyObject obj, Color value)
{
obj.SetValue(ColorProperty, value);
}
private static void OnColorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
OnElevationChanged(d, GetElevation((UIElement)d));
}
#endregion
In the CreateElevation method, add a new parameter for the color, so you can set it.
private static Effect CreateElevation(double elevation, Effect source, Color color)
{
...
shadow.Color = color;
...
}
Finally, in OnElevationChanged update it so it to call GetColor so it can pass in the new Color property.
private static object OnElevationChanged(DependencyObject d, object value)
{
if (d is UIElement element && value is double elevation)
if (elevation == 0)
element.Effect = null;
else
{
Effect e = CreateElevation(elevation, element.Effect, GetColor(element));
if (e != null)
element.Effect = e;
}
return value;
}
Example XAML
<Button
Width="100"
Height="20"
Content="shadow">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="local:UI.Elevation" Value="10" />
<Setter Property="local:UI.Color" Value="Blue" />
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="local:UI.Elevation" Value="5" />
<Setter Property="local:UI.Color" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
NOTE: There is some refactoring you could do to make all this cleaner, but hopefully this will get your started.

Related

Changing mouse over Foreground color of controls inside a Button in code behind

I've got this bit of XAML code. What it does is override the style of a button when mousing over. Here I use Green and PaleGreen for the sake of simplicity.
<Style TargetType="Button" x:Key="someNameHere">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}" BorderBrush="Green" BorderThickness="2,0,0,0">
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Background" Value="PaleGreen"/>
<Setter Property="Foreground" Value="Black"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Green"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</Style.Triggers>
</Style>
This works on both a standard button and more exotic ones, like so:
<Button Name="Standard" Content="Test" Style="{DynamicResource someNameHere}"/>
<Button Name="Exotic" Style="{DynamicResource someNameHere}">
<TextBlock Text="Test"/>
</Button>
<Button Name="MoreExotic" Style="{DynamicResource someNameHere}">
<Grid>
<TextBlock Text="Test"/>
</Grid>
</Button>
I want to do the same thing in code behind, on a custom button class. This is the code I have this far:
public class FlatButton : Button
{
public FlatButton() : base()
{
BorderThickness = new Thickness(2, 0, 0, 0);
SetStyle();
}
private void SetStyle()
{
Background = Brushes.PaleGreen;
Foreground = Brushes.Black;
BorderBrush = Brushes.Green;
Style style = new Style(typeof(Button), Style);
ControlTemplate template = new ControlTemplate(typeof(Button));
FrameworkElementFactory border = new FrameworkElementFactory(typeof(Border));
border.SetValue(Border.BackgroundProperty, new TemplateBindingExtension(Button.BackgroundProperty));
border.SetValue(Border.BorderBrushProperty, BorderBrush);
border.SetValue(Border.BorderThicknessProperty, BorderThickness);
border.Name = "someNameHere";
template.VisualTree = border;
FrameworkElementFactory content = new FrameworkElementFactory(typeof(ContentPresenter));
content.SetValue(Border.VerticalAlignmentProperty, System.Windows.VerticalAlignment.Center);
content.SetValue(Border.HorizontalAlignmentProperty, System.Windows.HorizontalAlignment.Center);
border.AppendChild(content);
Trigger trigger = new Trigger()
{
Property = Button.IsMouseOverProperty,
Value = true
};
trigger.Setters.Add(new Setter()
{
TargetName = "someNameHere",
Property = Button.BackgroundProperty,
Value = Brushes.Green
});
trigger.Setters.Add(new Setter()
{
TargetName = "someNameHere",
Property = Button.ForegroundProperty,
Value = Brushes.White
});
template.Triggers.Add(trigger);
Setter setter = new Setter()
{
Property = Button.TemplateProperty,
Value = template
};
style.Setters.Add(setter);
Style = style;
}
And applying it to a similar set of buttons:
<local:FlatButton x:Name="Standard" Content="Test"/>
<local:FlatButton x:Name="Exotic">
<TextBlock Text="Test"/>
</local:FlatButton>
<local:FlatButton x:Name="MoreExotic">
<Grid>
<TextBlock Text="Test"/>
</Grid>
</local:FlatButton>
The problem is this works fine on the "standard" button, but it doesn't change the Foreground color of the elements inside the "exotic" buttons like the XAML version does, and I haven't been able to find a solution so far.
So does anyone know how do I set the Foreground color of my content in code?
The three use cases I'm interested in are plaintext, TextBlock, and TextBlock inside a container like a Grid or StackPanel.
The Style that you are creating programmatically is not equivalent to the one you have defined in the XAML markup. This is:
private void SetStyle()
{
Style style = new Style(typeof(Button), Style);
ControlTemplate template = new ControlTemplate(typeof(Button));
FrameworkElementFactory border = new FrameworkElementFactory(typeof(Border));
border.SetValue(Border.BackgroundProperty, new TemplateBindingExtension(Button.BackgroundProperty));
border.SetValue(Border.BorderBrushProperty, Brushes.Green);
border.SetValue(Border.BorderThicknessProperty, new Thickness(2, 0, 0, 0));
template.VisualTree = border;
FrameworkElementFactory content = new FrameworkElementFactory(typeof(ContentPresenter));
content.SetValue(Border.VerticalAlignmentProperty, System.Windows.VerticalAlignment.Center);
content.SetValue(Border.HorizontalAlignmentProperty, System.Windows.HorizontalAlignment.Center);
border.AppendChild(content);
style.Setters.Add(new Setter()
{
Property = Button.BackgroundProperty,
Value = Brushes.PaleGreen
});
style.Setters.Add(new Setter()
{
Property = Button.ForegroundProperty,
Value = Brushes.Black
});
Trigger trigger = new Trigger()
{
Property = Button.IsMouseOverProperty,
Value = true
};
trigger.Setters.Add(new Setter()
{
Property = Button.BackgroundProperty,
Value = Brushes.Green
});
trigger.Setters.Add(new Setter()
{
Property = TextBlock.ForegroundProperty,
Value = Brushes.White
});
template.Triggers.Add(trigger);
Setter setter = new Setter()
{
Property = Button.TemplateProperty,
Value = template
};
style.Setters.Add(setter);
Style = style;
}

How to "expand" RichTextBox Paragraph's background color?

As you can see below, there are white margins from both sides, how to expand the gray background horizontally, so it will cover the TextBox from edge to edge?
<RichTextBox x:Name="logTextBox" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Auto" IsReadOnly="True" FontSize="12" Margin="10,165,10,10" >
<RichTextBox.Resources>
<Style TargetType="{x:Type Paragraph}">
<Setter Property="Margin" Value="0"/>
</Style>
<Style TargetType="ScrollViewer">
<Setter Property="MaxWidth" Value="480" />
</Style>
</RichTextBox.Resources>
</RichTextBox>
Usage:
public void AddLog(string log)
{
Run run = new Run(log);
Paragraph paragraph = new Paragraph(run);
paragraph.Background = new SolidColorBrush(Colors.Gray);
var numberOfBlocks = logTextBox.Document.Blocks.Count;
const int MaxNumberOfBlocks = 100;
if (numberOfBlocks > MaxNumberOfBlocks)
{
logTextBox.Document.Blocks.Remove(logTextBox.Document.Blocks.FirstBlock);
}
logTextBox.Document.Blocks.Add(paragraph);
logTextBox.ScrollToEnd();
}
Set the PagePadding property of the FlowDocument to 0 after the RichTextBox has been loaded:
public void AddLog(string log)
{
Run run = new Run(log);
Paragraph paragraph = new Paragraph(run);
paragraph.Background = new SolidColorBrush(Colors.Gray);
var numberOfBlocks = logTextBox.Document.Blocks.Count;
const int MaxNumberOfBlocks = 100;
if (numberOfBlocks > MaxNumberOfBlocks)
{
logTextBox.Document.Blocks.Remove(logTextBox.Document.Blocks.FirstBlock);
}
logTextBox.Document.Blocks.Add(paragraph);
logTextBox.Document.PagePadding = new Thickness(0); //<--
logTextBox.ScrollToEnd();
}

Blink tab custom header

I have been searching but I cant find much, I wanna make any of my dinamic tab headers background blink/flash when I call them to flash. I found this solution but I am kinda newbiew to binding and triggers on wpf so I couldnt do it.
The solution I found is this (Blink tab header on receiving event).
I will send some parts of my code below.
<TabControl x:Name="tabControlBTC" MouseLeftButtonDown="tabControlBTC_MouseLeftButtonDown" MouseRightButtonDown="tabControlBTC_MouseRightButtonDown" MouseDoubleClick="tabControlBTC_MouseDoubleClick"
TabStripPlacement="Left" Grid.Row="1" Grid.ColumnSpan="2" SelectionChanged="tabControlBTC_SelectionChanged">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Width" Value="240"/>
<Setter Property="Height" Value="40"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border Width="auto" Name="Border" Margin="3,0,0,0" CornerRadius="0" SnapsToDevicePixels="True">
<ContentPresenter VerticalAlignment="Center" ContentSource="Header" Margin="12,2,12,2"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True" SourceName="Border">
<Setter TargetName="Border" Property="Background" Value="#EDF5FA" />
<Setter TargetName="Border" Property="BorderThickness" Value="4, 0, 0, 0"/>
<Setter TargetName="Border" Property="BorderBrush" Value="DarkGray"/>
<Setter TargetName="Border" Property="Margin" Value="3,0,0,0"/>
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Foreground" Value="Black"/>
<Setter TargetName="Border" Property="Background">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#DAE9F3" Offset="0.432" />
<GradientStop Color="#DAE9F3" Offset="0.433" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter TargetName="Border" Property="BorderThickness" Value="0"/>
<Setter TargetName="Border" Property="BorderBrush" Value="DarkGray"/>
<Setter TargetName="Border" Property="Margin" Value="0"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ScrollViewer HorizontalScrollBarVisibility="Disabled" Background="#F4F4F5" VerticalScrollBarVisibility="Hidden" FlowDirection="LeftToRight">
<TabPanel x:Name="HeaderPanel" Panel.ZIndex ="0" KeyboardNavigation.TabIndex="1" IsItemsHost="true"/>
</ScrollViewer>
<ContentPresenter x:Name="PART_SelectedContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" ContentSource="SelectedContent" Grid.Column="1"/>
</Grid>
</ControlTemplate>
</TabControl.Template>
And how I create my tabitems headers dynamically.
UserControltest1 NewUserControlPage = new UserControltest1(contactNumber, username, jsonSellorBuy, 2);
newTab = new TabItem();
newTab.TabIndex = tabControlBTC.Items.Count + 1;
newTab.Name = "tab" + contactNumber;
PathGeometry CircleGeometry = new PathGeometry();
CircleGeometry.FillRule = FillRule.Nonzero;
PathFigureCollectionConverter pfcc = new PathFigureCollectionConverter();
CircleGeometry.Figures = (PathFigureCollection)pfcc.ConvertFrom("M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z");
System.Windows.Shapes.Path PathCirle = new System.Windows.Shapes.Path();
PathCirle.Fill = randomColor2();
PathCirle.Data = CircleGeometry;
var CanvasCirle = new Canvas() { Width = 24, Height = 24 };
TextBlock textBlockCircle = new TextBlock();
textBlockCircle.Text = username[0].ToString().ToUpper();
textBlockCircle.Foreground = new SolidColorBrush(Colors.Black);
textBlockCircle.Margin = new Thickness(0,3,0,3);
textBlockCircle.FontWeight = FontWeights.DemiBold;
textBlockCircle.TextAlignment = TextAlignment.Center;
textBlockCircle.Width = CanvasCirle.Width;
textBlockCircle.HorizontalAlignment = HorizontalAlignment.Center;
CanvasCirle.Children.Add(PathCirle);
CanvasCirle.Children.Add(textBlockCircle);
var viewBoxCircle = new Viewbox() { Width = 32, Height = 32, Margin = new Thickness(0, 0, 5, 0) };
viewBoxCircle.Child = CanvasCirle;
var stackPanel = new StackPanel() { Orientation = Orientation.Horizontal, Margin = new Thickness(-6, 0, 0, 0) };
var Buttoncircle = new Button() { Background = Brushes.Transparent, Margin = new Thickness(60, 0, 0, 0), BorderThickness = new Thickness(0) };
PathGeometry ButtonGeometry = new PathGeometry();
ButtonGeometry.FillRule = FillRule.Nonzero;
PathFigureCollectionConverter pfccc = new PathFigureCollectionConverter();
ButtonGeometry.Figures = (PathFigureCollection)pfccc.ConvertFrom("M13.46,12L19,17.54V19H17.54L12,13.46L6.46,19H5V17.54L10.54,12L5,6.46V5H6.46L12,10.54L17.54,5H19V6.46L13.46,12Z");
System.Windows.Shapes.Path PathCirleButton = new System.Windows.Shapes.Path();
PathCirleButton.Fill = Brushes.Black;
PathCirleButton.Data = ButtonGeometry;
Buttoncircle.Name = $"tab{contactNumber}";
var CanvasCirleButton = new Canvas() { Width = 24, Height = 24 };
CanvasCirleButton.Children.Add(PathCirleButton);
var viewBoxCircleButton = new Viewbox() { Width = 18};
viewBoxCircleButton.Child = CanvasCirleButton;
Buttoncircle.Content = viewBoxCircleButton;
Buttoncircle.Click += (s, e) => { Image_MouseDown(Buttoncircle); };
var stack = new StackPanel() { VerticalAlignment = VerticalAlignment.Top, Margin = new Thickness(0, 0, 0, 0) };
stack.Children.Add(new TextBlock() { Text = username, FontSize = 15 });
stack.Children.Add(new TextBlock() { Text = $"Trade #{contactNumber}", FontSize = 11 });
stackPanel.Children.Add(viewBoxCircle);
stackPanel.Children.Add(stack);
stackPanel.Children.Add(Buttoncircle);
newTab.Content = NewUserControlPage;
newTab.Header = stackPanel;
tabControlBTC.Items.Add(newTab);
tabControlBTC.SelectedItem = NewUserControlPage;
Your TabItem header is not just Text. it will be a Stackpanel contains other controls. Soi will simply animate the ContentControl.Opacity as shown below.
<Style x:Key="FlashingHeader" TargetType="TabItem">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<!--Make The Header -->
<ContentControl x:Name="header" Content="{Binding}" />
<!--Make The Background Flash-->
<DataTemplate.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="header"
Storyboard.TargetProperty="(ContentControl.Opacity)"
From="1" To="0" Duration="0:0:1" AutoReverse="True" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
You just added one TabItem to the TabControl. Add one or more items into TabControl to see IsMouseOver and IsSelected triggers from the style. The single item in the TabControl will be selected already. You cannot see the visual effects.

XAML Set Foreground for CustomControl when Row is Selected

I have a Datagrid where some columns are DataGridTemplateColumn like this
<DataGridTemplateColumn
Width="1.5*"
Header="{x:Static lang:Labels.GENERAL_ValorTotal}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<sty:DecimalTextBox
Text="{Binding Valor,StringFormat=\{0:c3\}}"
IsReadOnly="True"
Style="{StaticResource DecimalTextBoxGridStyle}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
My problem is when I select a row, My custom control doesn't change it's foreground color as DataGridTextColumn does.
How can i do to style my custom control or my datagrid to force all columns to change
Edit 1 My custom object.
public class DecimalTextBox : TextBox
{
#region Float Color
public static readonly DependencyProperty FloatColorProperty =
DependencyProperty.Register("FloatColor", typeof(Color), typeof(DecimalTextBox), new FrameworkPropertyMetadata(Colors.Red));
public Color FloatColor
{
get { return (Color)GetValue(FloatColorProperty); }
set { SetValue(FloatColorProperty, value); }
}
#endregion
#region Show Zero value
public bool ShowZeroValue
{
get { return (bool)GetValue(ShowZeroValueProperty); }
set { SetValue(ShowZeroValueProperty, value); }
}
// Using a DependencyProperty as the backing store for ShowZeroValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ShowZeroValueProperty =
DependencyProperty.Register("ShowZeroValue", typeof(bool), typeof(DecimalTextBox), new PropertyMetadata(true));
#endregion
protected TextBlock _textBlock;
protected FrameworkElement _textBoxView;
public DecimalTextBox()
{
_textBlock = new TextBlock() { Margin = new Thickness(1, 0, 0, 0) };
Loaded += ExTextBox_Loaded;
}
private void ExTextBox_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= ExTextBox_Loaded;
// hide the original drawing visuals, by setting opacity on their parent
var visual = this.GetChildOfType<DrawingVisual>();
if (visual != null)
{
_textBoxView = (FrameworkElement)visual.Parent;
_textBoxView.Opacity = 0;
// add textblock to do the text drawing for us
var grid = this.GetChildOfType<Grid>();
if (grid.Children.Count >= 2)
grid.Children.Insert(1, _textBlock);
else
grid.Children.Add(_textBlock);
}
}
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnLostKeyboardFocus(e);
_textBoxView.Opacity = 0;
_textBlock.Visibility = Visibility.Visible;
MustShowValue();
}
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
base.OnGotKeyboardFocus(e);
_textBoxView.Opacity = 1;
_textBlock.Visibility = Visibility.Collapsed;
}
private bool MustShowValue()
{
bool show = true;
if (!ShowZeroValue && Text == "0")
{
show = false;
_textBlock.Inlines.Clear();
_textBlock.Inlines.Add(new Run
{
Text = string.Empty,
FontFamily = FontFamily,
FontSize = FontSize,
Foreground = Foreground
});
_textBlock.Inlines.Add(new Run
{
Text = string.Empty,
FontFamily = FontFamily,
TextDecorations = System.Windows.TextDecorations.Underline,
BaselineAlignment = BaselineAlignment.TextTop,
FontSize = FontSize * 5 / 6,
Foreground = new SolidColorBrush(FloatColor)
});
}
return show;
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
if (MustShowValue())
{
// making sure text on TextBlock is updated as per TextBox
var dotPos = Text.IndexOf('.');
var textPart1 = dotPos == -1 ? Text : Text.Substring(0, dotPos + 1);
var textPart2 = (dotPos == -1 || dotPos >= (Text.Length - 1)) ? null : Text.Substring(dotPos + 1);
_textBlock.Inlines.Clear();
_textBlock.Inlines.Add(new Run
{
Text = textPart1,
FontFamily = FontFamily,
FontSize = FontSize,
Foreground = Foreground
});
if (textPart2 != null)
_textBlock.Inlines.Add(new Run
{
Text = textPart2,
FontFamily = FontFamily,
TextDecorations = System.Windows.TextDecorations.Underline,
BaselineAlignment = BaselineAlignment.TextTop,
FontSize = FontSize * 5 / 6,
Foreground = new SolidColorBrush(FloatColor)
});
}
}
}
public static class HelperExtensions
{
public static T GetChildOfType<T>(this DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
}
<Style x:Key="DecimalTextBoxGridStyle" TargetType="{x:Type local:DecimalTextBox}">
<Setter Property="TextAlignment" Value="Right"/>
<Setter Property="FloatColor" Value="Black"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="ShowZeroValue" Value="False"/>
<Style.Triggers>
<!--<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}}" Value="True">
<Setter Property="Foreground" Value="White"/>
</DataTrigger>-->
</Style.Triggers>
</Style>
You just need to update the inline(s) color (in inner TextBlock) every-time Foreground or FloatColor changes. The hard way would be add binding(s) between TextBox-properties and inline properties. The easy way would be to either add property changed callbacks to dependency property or just by overriding OnPropertyChanged:
For example (illustrates using OnPropertyChanged to keep control updated):
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == ForegroundProperty || e.Property == FloatColorProperty)
UpdateTextBlock(); //updates the text-block inlines
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
UpdateTextBlock(); // this can be merged to OnPropertyChanged (as text is also dependency property)
}
// new extracted method from OnTextChanged
private void UpdateTextBlock()
{
if (MustShowValue())
{
// making sure text on TextBlock is updated as per TextBox
var dotPos = Text.IndexOf('.');
var textPart1 = dotPos == -1 ? Text : Text.Substring(0, dotPos + 1);
var textPart2 = (dotPos == -1 || dotPos >= (Text.Length - 1)) ? null : Text.Substring(dotPos + 1);
_textBlock.Inlines.Clear();
_textBlock.Inlines.Add(new Run
{
Text = textPart1,
FontFamily = FontFamily,
FontSize = FontSize,
Foreground = Foreground
});
if (textPart2 != null)
_textBlock.Inlines.Add(new Run
{
Text = textPart2,
FontFamily = FontFamily,
TextDecorations = System.Windows.TextDecorations.Underline,
BaselineAlignment = BaselineAlignment.TextTop,
FontSize = FontSize * 5 / 6,
Foreground = new SolidColorBrush(FloatColor)
});
}
}
Same goes for FontFamily and FontSize too. This way whenever any property, that UpdateTextBlock() uses, is updated - whether through style or triggers - the control will know that it needs to update the inline(s) in inner TextBlock.
Update 08/27
Also, update your Style to set Foreground and FloatColor in setters, while using a MultiDataTrigger to account for both row's selected status and grid's focused state.
<Style x:Key="DecimalTextBoxGridStyle" TargetType="{x:Type local:DecimalTextBox}">
<Setter Property="TextAlignment" Value="Right"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FloatColor" Value="Black"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="ShowZeroValue" Value="False"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGridRow}}}" Value="True" />
<Condition Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=IsKeyboardFocusWithin}" Value="True" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Foreground" Value="White"/>
<Setter Property="FloatColor" Value="White"/>
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>
</Style>

wpf- green border with tick icon for correct textbox using Validation.HasError or another way

I have a Textbox that bind to a validation rule.
I am able to display red border when Validation.HasError is True
However, I am not able to display green border when user input is correct and I found that because my Trigger property replys on Validation.HasError and Validation.HasError IS NOT False when there is no validation error.
I wonder if there is a proper way or workaround to achieve this?
You can set the default Border to be Green, change it in Trigger, when Validation.HasError is true.
Using msdn exapmle, you can set BorderBrush and BorderThickness in the Style:
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Setter Property="BorderBrush" Value="Green"/>
<Setter Property="BorderThickness" Value="2"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="BorderBrush" Value="Red"/>
</Trigger>
<Trigger Property="TextBox.Text" Value="">
<Setter Property="BorderBrush" Value="Yellow"/>
</Trigger>
</Style.Triggers>
</Style>
Other parts of the code are
<TextBox Name="textBox1" Width="50" Height="30" FontSize="15" DataContext="{Binding}"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textBoxInError}"
Grid.Row="1" Grid.Column="1" Margin="2">
<TextBox.Text>
<Binding Path="Age"
UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:AgeRangeRule Min="21" Max="130"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
and
public class AgeRangeRule : ValidationRule
{
private int _min;
private int _max;
public AgeRangeRule()
{
}
public int Min
{
get { return _min; }
set { _min = value; }
}
public int Max
{
get { return _max; }
set { _max = value; }
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
int age = 0;
try
{
if (((string)value).Length > 0)
age = Int32.Parse((String)value);
}
catch (Exception e)
{
return new ValidationResult(false, "Illegal characters or " + e.Message);
}
if ((age < Min) || (age > Max))
{
return new ValidationResult(false,
"Please enter an age in the range: " + Min + " - " + Max + ".");
}
else
{
return new ValidationResult(true, null);
}
}
}

Categories