I want to animate button's background color with DinamicResources. To do this i create attached DependencyProperty:
namespace ModPlusStyle.Controls.Helpers
{
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
public class ButtonAssist
{
private static readonly Dictionary<Button, Color> _initBackgroundBrush = new Dictionary<Button, Color>();
private static readonly Dictionary<Button, Color> _initForegroundBrush = new Dictionary<Button, Color>();
public static readonly DependencyProperty AnimateMouseOverProperty = DependencyProperty.RegisterAttached(
"AnimateMouseOver",
typeof(bool),
typeof(ButtonAssist),
new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.AffectsRender, AnimateMouseOverChangedCallback));
public static void SetAnimateMouseOver(DependencyObject element, bool value)
{
element.SetValue(AnimateMouseOverProperty, value);
}
public static bool GetAnimateMouseOver(DependencyObject element)
{
return (bool)element.GetValue(AnimateMouseOverProperty);
}
private static void AnimateMouseOverChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Button button)
{
if ((bool)e.NewValue)
{
button.MouseEnter += ButtonOnMouseEnter;
button.MouseLeave += ButtonOnMouseLeave;
}
else
{
button.MouseEnter -= ButtonOnMouseEnter;
button.MouseLeave -= ButtonOnMouseLeave;
}
}
}
private static void ButtonOnMouseEnter(object sender, MouseEventArgs e)
{
if (sender is Button button &&
!(button.Parent is WindowCommands) &&
button.Background is SolidColorBrush backgroundSolidColorBrush &&
button.Foreground is SolidColorBrush foregroundSolidColorBrush)
{
var parentWindow = Window.GetWindow(button);
if (parentWindow != null)
{
if (parentWindow.Resources["WhiteBrush"] is SolidColorBrush whiteBrush &&
parentWindow.Resources["BlackBrush"] is SolidColorBrush blackBrush)
{
if (_initBackgroundBrush.ContainsKey(button))
_initBackgroundBrush[button] = backgroundSolidColorBrush.Color;
else
_initBackgroundBrush.Add(button, backgroundSolidColorBrush.Color);
if (_initForegroundBrush.ContainsKey(button))
_initForegroundBrush[button] = foregroundSolidColorBrush.Color;
else
_initForegroundBrush.Add(button, foregroundSolidColorBrush.Color);
button.Background = new SolidColorBrush(backgroundSolidColorBrush.Color);
ColorAnimation backgroundColorAnimation = new ColorAnimation(
backgroundSolidColorBrush.Color,
whiteBrush.Color,
new Duration(TimeSpan.FromMilliseconds(300)));
button.Background.BeginAnimation(SolidColorBrush.ColorProperty, backgroundColorAnimation);
button.Foreground = new SolidColorBrush(foregroundSolidColorBrush.Color);
ColorAnimation foregroundColorAnimation = new ColorAnimation(
foregroundSolidColorBrush.Color,
blackBrush.Color,
new Duration(TimeSpan.FromMilliseconds(300)));
button.Foreground.BeginAnimation(SolidColorBrush.ColorProperty, foregroundColorAnimation);
}
}
}
}
private static void ButtonOnMouseLeave(object sender, MouseEventArgs e)
{
if (sender is Button button &&
!(button.Parent is WindowCommands) &&
_initBackgroundBrush.ContainsKey(button) &&
_initForegroundBrush.ContainsKey(button))
{
var parentWindow = Window.GetWindow(button);
if (parentWindow != null)
{
if (parentWindow.Resources["AccentColorBrush"] is SolidColorBrush accentColorBrush &&
parentWindow.Resources["ForegroundForAccentedBrush"] is SolidColorBrush foregroundForAccentedBrush)
{
button.Background = new SolidColorBrush(((SolidColorBrush)button.Background).Color);
ColorAnimation backgroundColorAnimation = new ColorAnimation(
((SolidColorBrush)button.Background).Color,
_initBackgroundBrush[button],
new Duration(TimeSpan.FromMilliseconds(300)));
backgroundColorAnimation.Completed += (o, args) =>
{
if (_initBackgroundBrush[button] == accentColorBrush.Color)
button.SetResourceReference(Control.BackgroundProperty, "AccentColorBrush");
_initBackgroundBrush.Remove(button);
};
button.Background.BeginAnimation(SolidColorBrush.ColorProperty, backgroundColorAnimation);
button.Foreground = new SolidColorBrush(((SolidColorBrush)button.Foreground).Color);
ColorAnimation foregroundColorAnimation = new ColorAnimation(
((SolidColorBrush)button.Foreground).Color,
_initForegroundBrush[button],
new Duration(TimeSpan.FromMilliseconds(300)));
foregroundColorAnimation.Completed += (o, args) =>
{
if (_initForegroundBrush[button] == foregroundForAccentedBrush.Color)
button.SetResourceReference(Control.ForegroundProperty, "ForegroundForAccentedBrush");
_initForegroundBrush.Remove(button);
};
button.Foreground.BeginAnimation(SolidColorBrush.ColorProperty, foregroundColorAnimation);
}
}
}
}
}
}
I set value in button's style:
<Style x:Key="ModPlusAccentButton" TargetType="{x:Type ButtonBase}">
<Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
<Setter Property="Background" Value="{DynamicResource AccentColorBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource AccentColorBrush}" />
<Setter Property="Foreground" Value="{DynamicResource ForegroundForAccentedBrush}" />
<Setter Property="Padding" Value="12 6 12 6" />
<Setter Property="helpers:ButtonAssist.AnimateMouseOver" Value="True"></Setter>
<Setter Property="SnapsToDevicePixels" Value="True" />
.......
In debug i get the right colors in ButtonOnMouseEnter method. But button's background not changed
However, the animation in the ButtonOnMouseLeave method works correctly!
Why so?
The problem wath in ClipBorder that used in button's template. Change to Border and it start works
Related
I've used the CodeBox project from CodeProject and it works very well except for the fact that I can't disable text wrapping. In a normal TextBox, simply setting the TextWrapping property to NoWrap does the trick, but not with CodeBox (which inherits from TextBox in code-behind). I've tried adding a horizontal scrollbar but that doesn't help. The scrollbar is visible and it changes the size of the drag button to show that it sees that the unwrapped text is wider than the viewing area, but since the text has already been wrapped, dragging it doesn't make any difference.
I've tracked the problem to a line in OnRender:
"formattedText.MaxTextWidth = this.ViewportWidth; // space for scrollbar"
I'm fairly new to WPF and there's much to it that is still mysterious to me, so the solution may be obvious to someone with more experience with it.
I'd appreciate any suggestions. This is the code-behind C#, lengthy, but it has been trimmed down to only enough to show what's going on. The rest (that has been reomved) is just code that does more text-coloring.
public partial class CodeBox : TextBox
{
bool m_bScrollingEventEnabled;
SolidColorBrush m_brRed = new SolidColorBrush (Colors.Red);
SolidColorBrush m_brOrange = new SolidColorBrush (Colors.Orange);
SolidColorBrush m_brBlack = new SolidColorBrush (Colors.Black);
public CodeBox ()
{
this.TextChanged += new TextChangedEventHandler (txtTest_TextChanged);
this.Foreground = new SolidColorBrush (Colors.Transparent);
this.Background = new SolidColorBrush (Colors.Transparent);
this.TextWrapping = System.Windows.TextWrapping.NoWrap;
base.TextWrapping = System.Windows.TextWrapping.NoWrap;
InitializeComponent ();
}
public static DependencyProperty BaseForegroundProperty = DependencyProperty.Register ("BaseForeground", typeof (Brush), typeof (CodeBox),
new FrameworkPropertyMetadata (new SolidColorBrush (Colors.Black), FrameworkPropertyMetadataOptions.AffectsRender));
public Brush BaseForeground
{
get { return (Brush)GetValue (BaseForegroundProperty); }
set { SetValue (BaseForegroundProperty, value); }
}
public static DependencyProperty BaseBackgroundProperty = DependencyProperty.Register ("BaseBackground", typeof (Brush), typeof (CodeBox),
new FrameworkPropertyMetadata (new SolidColorBrush (Colors.Black), FrameworkPropertyMetadataOptions.AffectsRender));
public Brush BaseBackground
{
get { return (Brush)GetValue (BaseBackgroundProperty); }
set { SetValue (BaseBackgroundProperty, value); }
}
void txtTest_TextChanged (object sender, TextChangedEventArgs e)
{
this.InvalidateVisual ();
}
protected override void OnRender (System.Windows.Media.DrawingContext drawingContext)
{
//base.OnRender(drawingContext);
if (this.Text.Length > 0)
{
EnsureScrolling ();
FormattedText formattedText = new FormattedText (
this.Text,
CultureInfo.GetCultureInfo ("en-us"),
FlowDirection.LeftToRight,
new Typeface (this.FontFamily.Source),
this.FontSize,
BaseForeground); //Text that matches the textbox's
double leftMargin = 4.0 + this.BorderThickness.Left;
double topMargin = 2 + this.BorderThickness.Top;
***formattedText.MaxTextWidth = this.ViewportWidth; // space for scrollbar***
formattedText.MaxTextHeight = Math.Max (this.ActualHeight + this.VerticalOffset, 0); //Adjust for scrolling
drawingContext.PushClip (new RectangleGeometry (new Rect (0, 0, this.ActualWidth, this.ActualHeight)));//restrict text to textbox
int iStartVisibleLine = GetFirstVisibleLineIndex ();
int iEndVisibleLine = GetLastVisibleLineIndex ();
for (int iIdx = iStartVisibleLine; iIdx <= iEndVisibleLine - 1; ++iIdx)
{
// Text coloring
int iOffset = GetCharacterIndexFromLineIndex (iIdx);
int iOffsetNext = GetCharacterIndexFromLineIndex (iIdx + 1);
string strLine = Text.Substring (iOffset, iOffsetNext - iOffset);
}
drawingContext.DrawText (formattedText, new Point (leftMargin, topMargin - this.VerticalOffset));
}
}
private void EnsureScrolling ()
{
if (!m_bScrollingEventEnabled)
{
DependencyObject dp = VisualTreeHelper.GetChild (this, 0);
ScrollViewer sv = VisualTreeHelper.GetChild (dp, 0) as ScrollViewer;
sv.ScrollChanged += new ScrollChangedEventHandler (ScrollChanged);
m_bScrollingEventEnabled = true;
}
}
private void ScrollChanged (object sender, ScrollChangedEventArgs e)
{
this.InvalidateVisual ();
}
}
The xaml from the project:
<TextBox x:Class="CodeBoxControl.CodeBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:CodeBoxControl">
<TextBox.Template>
<ControlTemplate TargetType="c:CodeBox" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Border BorderThickness="{TemplateBinding Border.BorderThickness}" BorderBrush="{TemplateBinding Border.BorderBrush}"
Background="{TemplateBinding Panel.Background}" Name="Bd" SnapsToDevicePixels="True">
<ScrollViewer Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="UIElement.IsEnabled">
<Setter Property="Panel.Background" TargetName="Bd">
<Setter.Value>
<DynamicResource ResourceKey="{x:Static SystemColors.ControlBrushKey}" />
</Setter.Value>
</Setter>
<Setter Property="TextElement.Foreground">
<Setter.Value>
<DynamicResource ResourceKey="{x:Static SystemColors.GrayTextBrushKey}" />
</Setter.Value>
</Setter>
<Trigger.Value>
<s:Boolean>False</s:Boolean>
</Trigger.Value>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</TextBox.Template>
Xaml from the parent window that uses the CodeBox code:
<c:CodeBox Name="DisassemblyOutput"
FontFamily="Courier New"
FontSize="20"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto"
BaseForeground="Black"
Margin="4,4,4,4"
Background="#CEE9C9"
Foreground="Magenta"
TextWrapping="NoWrap"
AutoWordSelection="False"/>
This is a sample of the code that loads the text into the CodeBox window:
private void OnLoadDASM (object sender, RoutedEventArgs e)
{
DisassemblyOutput.FontSize = 12;
DisassemblyOutput.Clear ();
DisassemblyOutput.m_eWindowData = CodeBox.EWindowData.EDasm;
DisassemblyOutput.Background = new SolidColorBrush (Colors.Transparent);//Color.FromRgb (0xCE, 0xE9, 0xC9));
DisassemblyOutput.Foreground = new SolidColorBrush (Colors.Transparent);
DisassemblyOutput.BaseBackground = new SolidColorBrush (Color.FromRgb (0xCE, 0xE9, 0xC9));
DisassemblyOutput.BaseForeground = new SolidColorBrush (Colors.Transparent);
DisassemblyOutput.TextWrapping = TextWrapping.NoWrap;
DisassemblyOutput.Text += "Loop_02_0A0F 0A0F: SIO F3 10 28 5475 Keyboard Set Error Indicator Restore Data Key " + Environment.NewLine;
DisassemblyOutput.Text += " Disable Interrupt " + Environment.NewLine;
DisassemblyOutput.Text += " 0A12: SNS 70 12 FF,1 0x0B0C 5475 Keyboard 2 sense bytes " + Environment.NewLine;
}
This is what I want:
https://i.stack.imgur.com/M4ts0.png
and what's showing up:
https://i.stack.imgur.com/gdBco.png
I've also noticed that when the text wraps and I use the vertical scrollbar, the text in the top part of the pane disappears, and the more I scroll down, the more of it disappears:
1
The fix is to set MaxTextWidth to the width of the line instead of the ViewportWidth property:
iStartVisibleLine = GetFirstVisibleLineIndex ();
iEndVisibleLine = GetLastVisibleLineIndex ();
iOffset = GetCharacterIndexFromLineIndex (0);
iOffsetNext = GetCharacterIndexFromLineIndex (1);
strLine = Text.Substring (iOffset, iOffsetNext - iOffset);
geomFirstLine = formattedText.BuildHighlightGeometry (new Point (leftMargin, topMargin - this.VerticalOffset), iOffset, strLine.Length);
rcBounds = geomFirstLine.GetRenderBounds (null);
formattedText.MaxTextWidth = rcBounds.Width; // Space for scrollbar
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.
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>
I copied unit based textbox class from internet and class definition looks as follow:
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SapQmWp.Classes
{
public class UnitTextBox : TextBox
{
public static DependencyProperty UnitTextProperty =
DependencyProperty.Register(
"UnitText",
typeof(string),
typeof(UnitTextBox),
new FrameworkPropertyMetadata(
default(string),
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender));
public static DependencyProperty UnitPaddingProperty =
DependencyProperty.Register(
"UnitPadding",
typeof(Thickness),
typeof(UnitTextBox),
new FrameworkPropertyMetadata(
new Thickness(5d, 0d, 0d, 0d),
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender));
public static DependencyProperty TextBoxWidthProperty =
DependencyProperty.Register(
"TextBoxWidth",
typeof(double),
typeof(UnitTextBox),
new FrameworkPropertyMetadata(
double.NaN,
FrameworkPropertyMetadataOptions.AffectsMeasure));
private FormattedText _unitText;
private Rect _unitTextBounds;
public string UnitText
{
get { return (string) GetValue(UnitTextProperty); }
set { SetValue(UnitTextProperty, value); }
}
public Thickness UnitPadding
{
get { return (Thickness) GetValue(UnitPaddingProperty); }
set { SetValue(UnitPaddingProperty, value); }
}
public double TextBoxWidth
{
get { return (double) GetValue(TextBoxWidthProperty); }
set { SetValue(TextBoxWidthProperty, value); }
}
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == ForegroundProperty)
EnsureUnitText(true);
}
protected override Size MeasureOverride(Size constraint)
{
var textBoxWidth = TextBoxWidth;
var unit = EnsureUnitText(true);
var padding = UnitPadding;
if (unit != null)
{
var unitWidth = unit.Width + padding.Left + padding.Right;
var unitHeight = unit.Height + padding.Top + padding.Bottom;
constraint = new Size(
constraint.Width - unitWidth,
Math.Max(constraint.Height, unitHeight));
}
var hasFixedTextBoxWidth = !double.IsNaN(textBoxWidth) &&
!double.IsInfinity(textBoxWidth);
if (hasFixedTextBoxWidth)
constraint = new Size(textBoxWidth, constraint.Height);
var baseSize = base.MeasureOverride(constraint);
var baseWidth = hasFixedTextBoxWidth ? textBoxWidth : baseSize.Width;
if (unit != null)
{
var unitWidth = unit.Width + padding.Left + padding.Right;
var unitHeight = unit.Height + padding.Top + padding.Bottom;
return new Size(
baseWidth + unitWidth,
Math.Max(baseSize.Height, unitHeight));
}
return new Size(baseWidth, baseSize.Height);
}
protected override Size ArrangeOverride(Size arrangeBounds)
{
var textSize = arrangeBounds;
var unit = EnsureUnitText(false);
var padding = UnitPadding;
if (unit != null)
{
var unitWidth = unit.Width + padding.Left + padding.Right;
var unitHeight = unit.Height + padding.Top + padding.Bottom;
textSize.Width -= unitWidth;
_unitTextBounds = new Rect(
textSize.Width + padding.Left,
(arrangeBounds.Height - unitHeight)/2 + padding.Top,
textSize.Width,
textSize.Height);
}
var baseSize = base.ArrangeOverride(textSize);
if (unit != null)
{
var unitWidth = unit.Width + padding.Left + padding.Right;
var unitHeight = unit.Height + padding.Top + padding.Bottom;
return new Size(
baseSize.Width + unitWidth,
Math.Max(baseSize.Height, unitHeight));
}
return baseSize;
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
var unitText = EnsureUnitText(false);
if (unitText != null)
drawingContext.DrawText(unitText, _unitTextBounds.Location);
}
private FormattedText EnsureUnitText(bool invalidate = false)
{
if (invalidate)
_unitText = null;
if (_unitText != null)
return _unitText;
var unit = UnitText;
if (!string.IsNullOrEmpty(unit))
{
_unitText = new FormattedText(
unit,
CultureInfo.InvariantCulture,
FlowDirection,
new Typeface(
FontFamily,
FontStyle,
FontWeight,
FontStretch),
FontSize,
Foreground);
}
return _unitText;
}
}
}
And I insert into the window xaml as follow:
...
<ui:UnitTextBox Grid.Row="1" Style="{StaticResource WeightTbStyle}" UnitText="KG" Text="{Binding TargetWeight, UpdateSourceTrigger=PropertyChanged}"/>
...
But the background color is null rather than a color.
My question, how to set background color for UnitText too?
Update
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SapQmWp.Themes">
<Style x:Key="WeightTbStyle" TargetType="TextBox">
<Setter Property="Background" Value="#F8F8F8" />
<Setter Property="BorderBrush" Value="{x:Null}"/>
<Setter Property="Margin" Value="10,40,10,40" />
<Setter Property="Padding" Value="0,0,0,5" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Foreground" Value="#404242"/>
<Setter Property="FontSize" Value="24pt"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="BorderThickness" Value="0" />
</Style>
</ResourceDictionary>
Update 2
When I set the background on controls like
<ui:UnitTextBox Grid.Row="1" Background="Tomato" Style="{StaticResource WeightTbStyle}" UnitText="KG" Text="{Binding TargetWeight, UpdateSourceTrigger=PropertyChanged}"/>
as result I've got:
In your
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
var unitText = EnsureUnitText(false);
if (unitText != null)
drawingContext.DrawText(unitText, _unitTextBounds.Location);
}
Try something like this to set color and formatting
var formattedText = new FormattedText(unitText,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(new FontFamily("ANY_FONT_FAMILY"), FontStyles.Normal, FontWeights.Bold, FontStretches.Normal),
24, Brushes.Red);
drawingContext.DrawText(formattedText, _unitTextBounds.Location);
}
And for background color just draw a rectangle before you render your text.
drawingContext.DrawRectangle(Brushes.Red, Nothing, YOUR_TEXT_RECT);
I am creating a textbox dynamically. I have 2 columns in my grid. I want to add new textbox to the row if the other textbox value="tea". I want to create new textbox to corresponding row textbox value change. I am unable to use Tag to get selected row here. because I have already used Tag for some purpose. I don't have much idea about Tag. Anyhow, how can I add new textbox to the column1 to the corresponding row?
This is my code..
public int count = 1;
public TextBox txt1;
private void btn_addnew_Click(object sender, RoutedEventArgs e)
{
//Creating Rows..
RowDefinition row0 = new RowDefinition();
row0.Height = new GridLength(40);
grid1.RowDefinitions.Add(row0);
//Creating columns..
ColumnDefinition col0 = new ColumnDefinition();
ColumnDefinition col1 = new ColumnDefinition();
col0.Width = new GridLength(150);
col1.Width = new GridLength(250);
grid1.ColumnDefinitions.Add(col0);
grid1.ColumnDefinitions.Add(col1);
int i = count;
//1st Column TextBox
txt1 = new TextBox();
txt1.Margin = new Thickness(10, 10, 0, 0);
Grid.SetRow(txt1, i);
Grid.SetColumn(txt1, 0);
txt1.Tag = txt1;
txt1.MouseEnter+=txt1_MouseEnter;
txt1.TextChanged += txt1_TextChanged;
grid1.Children.Add(txt1);
count++;
}
private void txt1_MouseEnter(object sender, MouseEventArgs e)
{
txt1 = ((TextBox)sender).Tag as TextBox;
popup.IsOpen = true;
}
public TextBox txt2;
private void txt1_TextChanged(object sender, TextChangedEventArgs e)
{
if (txt1.Text.ToString() == "Tea")
{
txt2 = new TextBox();
//How to set row here?
Grid.SetRow(txt2, ??);
Grid.SetColumn(txt2, 1);
txt2.Margin = new Thickness(10, 10, 0, 0);
grid1.Children.Add(txt2);
}
else
{
grid1.Children.Remove(txt2);
}
}
If you just want to achieve this, then more elegant way will be to add the user control to your application like below:
<UserControl x:Class="WpfApplication4.TestUserControl"
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">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox Name="TextBox1"/>
<TextBox Grid.Column="1">
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=TextBox1, UpdateSourceTrigger=PropertyChanged}" Value="tea">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Grid>
</UserControl>
then in your btn_addnew_Click() method, you just need to add this usercontrol to user Grid and assign row and column to it. Showing/Hiding of textbox will be taken care of by teh user control itself.
var userControl = new MyUserControl();
userControl .Margin = new Thickness(10, 10, 0, 0);
Grid.SetRow(userControl , i);
grid1.Children.Add(userControl );
OR
if you want to have value of Grid.Row for textbox1 you can get it directly as:
if (textbox1 != null)
{
int row = (int)textbox1.GetValue(Grid.RowProperty);
Grid.SetRow(txt2, row);
}
Attached properties can be used to store such information so define an attached property in the class
public static int GetGridRow(DependencyObject obj)
{
return (int)obj.GetValue(GridRowProperty);
}
public static void SetGridRow(DependencyObject obj, int value)
{
obj.SetValue(GridRowProperty, value);
}
// Using a DependencyProperty as the backing store for GridRow. This enables animation, styling, binding, etc...
public static readonly DependencyProperty GridRowProperty =
DependencyProperty.RegisterAttached("GridRow", typeof(int), typeof(ViewModel), new PropertyMetadata(0));
then use it to store the value of the row
private void btn_addnew_Click(object sender, RoutedEventArgs e)
{
//1st Column TextBox
txt1 = new TextBox();
Grid.SetRow(txt1, i);
SetGridRow(text1, i);
...
grid1.Children.Add(txt1);
count++;
}
then use it when u need it
//How to set row here?
Grid.SetRow(txt2, GetGridRow(txt1));
Grid.SetColumn(txt2, 1);