How to add a background to a textbox cue banner - c#

This question showed me how to add a watermark text to my TextBox. I've tried to implement it in my project, but it replaces the background of my TextBox.
Because my panel has a different color, that panel color is shown through the textbox. How can I set this correctly?
I've tried to set the Background of the Label to white, but that doesn't work because it isn't stretched.
<TextBox>
<TextBox.Style>
<Style TargetType="TextBox" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style.Resources>
<VisualBrush x:Key="CueBannerBrush" AlignmentX="Left" AlignmentY="Center" Stretch="Uniform">
<VisualBrush.Visual>
<!-- set the background to white -->
<Label Content="Search" Foreground="LightGray" Background="White"/>
</VisualBrush.Visual>
</VisualBrush>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
This gives the following:
But setting Stretch to Fill gives this result with stretched text:

This might not be the best solution, but if my controls are supposed to behave differently from what they are ment to, I like to create them myself. That way I got full control and know exactly what happens when i do this and that.
class TextBoxWaterMark : TextBox
{
#region Datafields
private string pm_WaterMark = "";
#endregion
#region Constructor
public TextBoxWaterMark()
{
}
#endregion
#region Control events
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
if ((string)this.Tag != "")
{
this.Foreground = new SolidColorBrush(Colors.Black);
this.Text = "";
}
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
if ((string)this.Tag != "")
{
if (this.Text == "")
{
this.Text = pm_WaterMark;
this.Foreground = new SolidColorBrush(Colors.Gray);
}
}
}
#endregion
#region Public get and set methods
public string WaterMark
{
get { return pm_WaterMark; }
set
{
pm_WaterMark = value;
this.Text = pm_WaterMark;
this.Foreground = new SolidColorBrush(Colors.Gray);
}
}
#endregion
and then in my XAML code i simply add it whereever i want like this.
<Form:TextBoxWaterMark WaterMark="Insert watermark text here" />
Hope this is what you are looking for :P

You can add something behind your textbox :
<StackPanel background="white">
<textbox>
</textbox>
</StackPanel>
I think that the right part of your text box is transparent, am I right ?
I also think that there are better component than a StackPanel to do this.

Related

Binding gets an old value out of nowhere

I have a control which derives from a TextBox and has 2 additional string properties: Formula and FormulaResult (readonly):
public class SpreadsheetCellControl : TextBox, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
public string Formula
{
get;
set;
}
public string FormulaResult
{
get
{
if (Formula != null && Formula != "")
{
string formula = Formula;
formula = formula.StartsWith("=") ? formula.Substring(1) : formula;
var calculation = Calculator.Calculate(formula);
return calculation.Result.ToString();
}
else return "";
}
}
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
Formula = Text;
base.OnPreviewLostKeyboardFocus(e);
}
}
As defined in XAML, Text is bound to Formula when the control is focused and FormulaResult when it is not focused:
<Style TargetType="local:SpreadsheetCellControl" BasedOn="{StaticResource HoverableStyle}">
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Text" Value="{Binding RelativeSource={RelativeSource self}, Path=FormulaResult, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/>
<Style.Triggers>
<Trigger Property="IsFocused" Value="true">
<Setter Property="BorderThickness" Value="2" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="Text" Value="{Binding Formula, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</Trigger>
</Style.Triggers>
</Style>
Everything works as it should. For instance, I click the control, type for example "1/2", click anywhere else and it shows "0.5".
Then I set the Formula property from code-behind to "3" (any value actually). Before I click on the control, it shows all three properties correct: 3, 3 and 3. But when I click on the control, the Text property suddenly becomes the old "1/2", which is not stored anywhere. I checked that by placing a TextBlock and binding its Text to all three values of the cell control. Debugging also showed only new values.
P.S. Also "Formula" does not seem to take values by itself from "Text" here: Setter Property="Text" Value="{Binding Formula, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
The Binding in the Text property Setter inside the Trigger is missing RelativeSource Self:
<Setter Property="Text"
Value="{Binding Path=Formula,
RelativeSource={RelativeSource Self},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>

Xamarin Forms: How to set all labels style in one liner code?

So I have a XF application with multiple Labels. I want to be able to change all my label's FontStyle programmatically depending on the platform in one line if possible like below instead of listing the label's name one by one.
if(Device.RuntimePlatform == Device.Android) {
label.Style = Device.Styles.ListItemDetailTextStyle;
} else if(Device.RuntimePlatform == Device.iOS){
label.Style = Device.Styles.ListItemTextStyle;
}
Is this possible?
You should set style in App.xaml file. I needed to set default fonts for all Labels in application (including new classes derived from Label)
<Style ApplyToDerivedTypes="true" TargetType="Label">
<Style.Triggers>
<Trigger TargetType="Label" Property="FontAttributes" Value="None">
<Setter Property="FontFamily" Value="{StaticResource MainFontFamily}" />
</Trigger>
<Trigger TargetType="Label" Property="FontAttributes" Value="Bold">
<Setter Property="FontFamily" Value="{StaticResource MainFontFamilyBold}" />
</Trigger>
</Style.Triggers>
<Setter Property="TextColor" Value="Black" />
</Style>
And set fonts like
<OnPlatform x:Key="MainFontFamily" x:TypeArguments="x:String">
<On Platform="Android">font_file_name.ttf#Font Name</On>
<On Platform="iOS">Font Name</On>
</OnPlatform>
You can read more in here about styles
Alternative option is to simply extend Label:
public class MyLabel : Label
{
public MyLabel()
{
if (Device.RuntimePlatform == Device.Android)
{
Style = Device.Styles.ListItemDetailTextStyle;
}
else if (Device.RuntimePlatform == Device.iOS)
{
Style = Device.Styles.ListItemTextStyle;
}
}
}
P.S.: Styling is nicely covered in official documentation.

Wpf Style Trigger triggered only once

I have added a property trigger on a grid as below
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="ToolTip" Value=""></Setter>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="ToolTip" Value="{Binding StoredValue}"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</Grid.Style>
The property is triggered only once when I hover over the grid.
What I require is that the property's (StoredValue) getter has to be called every time when MouseHover happens.
Please help
If you really want to update the tooltip every time it is displayed, you can utilize the ToolTipOpening event to refresh the binding:
<Grid x:Name="grid1" Background="Transparent">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="ToolTip" Value="{Binding StoredValue,TargetNullValue=''}"/>
<EventSetter Event="ToolTipOpening" Handler="grid1_ToolTipOpening"/>
</Style>
</Grid.Style>
</Grid>
Update the binding in code behind:
private void grid1_ToolTipOpening(object sender, ToolTipEventArgs e)
{
var s = sender as FrameworkElement;
var be = BindingOperations.GetBindingExpressionBase(s, FrameworkElement.ToolTipProperty);
if (be != null)
{
be.UpdateTarget();
}
}
Note: the TargetNullValue='' is necessary in case StoredValue would sometimes return a null. Otherwise the Tooltip wouldn't attempt to open and thus the ToolTipOpening would never happen and the value would never update from the null to a new value.
While I can't explain the nature of problem, here is a quick workaround: rise notification manually, then binding will refresh itself. You trade trigger for event:
<Grid Background="Transparent" MouseEnter="Grid_MouseEnter">
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<!-- normal binding, this line is comment and should be gray -->
<Setter Property="ToolTip" Value="{Binding StoredValue}" />
</Style>
</Grid.Style>
</Grid>
public partial class MainWindow : Window, INotifyPropertyChanged
{
public string StoredValue => "123"; // is called every time mouse is entered
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
// rise notification manually
void Grid_MouseEnter(object sender, MouseEventArgs e) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StoredValue)));
}

How to change custom control's background color based on its property

I have simple custom control that shows a message to user (something like browser's Info bar).
I have added a Boolean Dependency Property that indicate an error message. If flag is set the background color of control should be red otherwise yellow.
Here is style for the control(in Themes\Generic.xaml):
<Style TargetType="{x:Type local:InfoBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:InfoBar}">
<ControlTemplate.Triggers>
<Trigger Property="IsError" Value="True" >
<Setter Property="Background" Value="LightPink" />
</Trigger>
<Trigger Property="IsError" Value="False" >
<Setter Property="Background" Value="LightYellow" />
</Trigger>
</ControlTemplate.Triggers>
<Grid Margin="4,0,4,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBlock Text="{TemplateBinding Message}" Padding="5" FontWeight="Normal" TextWrapping="Wrap" Grid.Column="0"/>
<Button x:Name="PART_CloseButton" Grid.Column="1" VerticalAlignment="Top" >
<Button.Template>
<ControlTemplate>
<Border HorizontalAlignment="Center" VerticalAlignment="Center" BorderBrush="Transparent">
<Image Height="16" Width="16" Source="/QOffice.Common.Controls;component/Images/icons/Close.png" />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here is The control itself:
[TemplatePart(Name = PART_CloseButton, Type = typeof(ButtonBase))]
public class InfoBar : Control
{
private const string PART_CloseButton = "PART_CloseButton";
static InfoBar()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(InfoBar), new FrameworkPropertyMetadata(typeof(InfoBar)));
}
#region CloseButton
private ButtonBase _closeButton;
/// <summary>
/// Gets or sets the CloseButton template part.
/// </summary>
private ButtonBase CloseButton
{
get
{
return _closeButton;
}
set
{
if (_closeButton != null)
{
_closeButton.Click -= OnButtonClick;
}
_closeButton = value;
if (_closeButton != null)
{
_closeButton.Click += OnButtonClick;
}
}
}
private void OnButtonClick(object sender, RoutedEventArgs e)
{
this.Visibility = System.Windows.Visibility.Collapsed;
}
#endregion
public override void OnApplyTemplate()
{
CloseButton = GetTemplateChild(PART_CloseButton) as ButtonBase;
}
#region DependencyProperty Message of InfoBar
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message", typeof(string), typeof(InfoBar),
new UIPropertyMetadata());
#endregion
#region DependencyProperty IsError of InfoBar
public bool IsError
{
get { return (bool)GetValue(IsErrorProperty); }
set { SetValue(IsErrorProperty, value); }
}
public static readonly DependencyProperty IsErrorProperty =
DependencyProperty.Register("IsError", typeof(bool), typeof(InfoBar),
new UIPropertyMetadata());
#endregion
}
As you can see I have defined a property IsError and a trigger to set the background of the control.
But the background is always transparent. Other than that the control if functional.
What is wrong?
It seems that your Custom Control is not setting Background Color properly even if I add Background color manually. I am not sure why this is, hopefully someone can elaborate. I did fix your issue though by changing the color of the Grid in your style using:
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:InfoBar}}, Path=IsError}" Value="True">
<Setter Property="Background" Value="LightPink" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:InfoBar}}, Path=IsError}" Value="False">
<Setter Property="Background" Value="LightYellow" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
This triggers the background color of the grid based on the IsError value in your InfoBar control.
You give your Control a Background but no child is using it. Two possible solutions:
TemplateBinding
<Grid Margin="4,0,4,0" Background="{TemplateBinding Background}">
DataTrigger with TargetName
<ControlTemplate TargetType="{x:Type local:InfoBar}">
<Grid Name="grid" Margin="4,0,4,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
...
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsError" Value="True" >
<Setter TargetName="grid" Property="Background" Value="LightPink" />
</Trigger>
<Trigger Property="IsError" Value="False" >
<Setter TargetName="grid" Property="Background" Value="LightYellow" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
In this solution you have to change the order: ControlTemplate.Triggers after Grid declaration.
Try this (Since IsError is DP in your InfoBar and not property of your ControlTemplate)
<DataTrigger Property="{Binding IsError, RelativeSource={RelativeSource
Mode=TemplatedParent}" Value="True">
<Setter Property="Background" Value="LightPink" />
</DataTrigger>
<DataTrigger Property="{Binding IsError, RelativeSource={RelativeSource
Mode=TemplatedParent}" Value="False">
<Setter Property="Background" Value="LightYellow" />
</DataTrigger>

WPF: Launch code when IsMouseOver ComboBoxItem

I have a ComboBox. Without changing the template, is there a way that I can launch code when there user places their mouse over a ComboBoxItem, but before the selection actually occurs? It seems like I should be able to specify an EventTrigger or a Trigger to do this in the style of ComboBoxItem.
<ComboBox Grid.Column="1" Grid.Row="0"
ItemsSource="{Binding Voices}"
SelectedItem="{Binding SelectedVoice, Mode=TwoWay}">
<ComboBox.Resources>
<Style TargetType="{x:Type ComboBoxItem}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
... Launch my code from code behind... but HOW? ...
</Trigger>
</Style.Triggers>
</Style>
</ComboBox.Resources>
</ComboBox>
I'm also ok with ousing a MouseEnter, but I would rather not build a separate DataTemplate or ContentTemplate if possible.
Update. The idea behind this snippet is to Play test audio when the user hovers over a new voice, which I would have to do from the code side. Help!
You can use EventSetter:
<ComboBox.Resources>
<Style TargetType="{x:Type ComboBoxItem}">
<EventSetter Event="PreviewMouseMove" Handler="ComboBoxItem_PreviewMouseMove" />
</Style>
</ComboBox.Resources>
in code behind:
private void ComboBoxItem_PreviewMouseMove(object sender, MouseEventArgs e)
{
ComboBoxItem item = sender as ComboBoxItem;
//Now you can use this Item
}
I know a dirty solution.. just in case you run out of solutions try this as your last hope..
I tested this by creating a textblock in XAML and setting its text equal to content of comboboxitem once mouse is over it and setting text to "" once mouse has left
I am using AttachedBehaviours to find out on which particular comboboxitem is mouse over once mouse is there and also getting notified once mouse is not over it anymore or mouse is left
Try this.. create a class
public static class ComboBoxBehaviour
{
//holding reference of MainWindow class to update the textBlock
public static MainWindow windoewRef ;
public static bool GetTest(ComboBoxItem target)
{
return (bool)target.GetValue(TestAttachedProperty);
}
public static void SetTest(ComboBoxItem target, bool value)
{
target.SetValue(TestAttachedProperty, value);
}
public static readonly DependencyProperty TestAttachedProperty = DependencyProperty.RegisterAttached("Test", typeof(bool), typeof(ComboBoxBehaviour), new UIPropertyMetadata(false, OnMouseOverChanged));
static void OnMouseOverChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
ComboBoxItem item = o as ComboBoxItem;
if ((bool)e.NewValue)
{
// I am setting text of a textblock for testing once mouse is over an item
windoewRef.textBlock.Text = item.Content.ToString();
}
else
{
//setting text to "" once mouse has been moved
windoewRef.textBlock.Text = "";
}
}
}
In XAML
<TextBlock Text="" x:Name="textBlock" />
<ComboBox x:Name="combo">
<ComboBox.Resources>
<Style TargetType="{x:Type ComboBoxItem}" xmlns:behaviours="clr-namespace:WpfApplication1">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="behaviours:ComboBoxBehaviour.Test" Value="True"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="False">
<Setter Property="behaviours:ComboBoxBehaviour.Test" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
</ComboBox.Resources>
</ComboBox>
I know this is a bad solution and it may have problems which I haven't found yet but just my thoughts...

Categories