I'm trying to find out a way to apply a style to a TextBox element when it does not contain text. I want the TextBox to have a different background color (for instance) when it does or does not contain any text.
As Triggers are not something that I can use in Silverlight (afaik), is there another way to do this? Preferrably without writing a custom implementation of TextBox just for this behavior. Thanks.
I ended up using a default behavior (ConditionBehavior):
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:Interaction.Behaviors>
<ec:ConditionBehavior>
<ec:ConditionalExpression>
<ec:ComparisonCondition LeftOperand="{Binding Text, ElementName=textBox}" RightOperand="" Operator="NotEqual"/>
</ec:ConditionalExpression>
</ec:ConditionBehavior>
</i:Interaction.Behaviors>
<ec:ChangePropertyAction PropertyName="Background" Value="{StaticResource PhoneTextBoxBrush}" />
</i:EventTrigger>
<i:EventTrigger EventName="TextChanged">
<i:Interaction.Behaviors>
<ec:ConditionBehavior>
<ec:ConditionalExpression>
<ec:ComparisonCondition LeftOperand="{Binding Text, ElementName=textBox}" RightOperand="" Operator="Equal"/>
</ec:ConditionalExpression>
</ec:ConditionBehavior>
</i:Interaction.Behaviors>
<ec:ChangePropertyAction PropertyName="Background" Value="Transparent" />
</i:EventTrigger>
</i:Interaction.Triggers>
This should be fairly easy to accomplish with a custom behavior. Use this link to create a behavior that can be attached to a TextBox control. In the OnAttached method, you can handle the TextChanged event of even the LostFocus method to check whether the TextBox is empty or not. Accordingly, you can switch the style between the styles.
P.S: You might need to call the TextBox.ApplyTemplate() method after changing the style. Note sure about that though.
Create your styles
<Style x:Key="FilledStyle" TargetType="TextBox">
<Setter Property="Background" Value="Beige" />
</Style>
<Style x:Key="EmptyStyle" TargetType="TextBox">
<Setter Property="Background" Value="Yellow" />
</Style>
<Models:TextStyleConverter x:Key="TextStyleConverter" />
Create Converter
public class TextStyleConverter : IValueConverter
{
#region Implementation of IValueConverter
/// <summary>
/// Modifies the source data before passing it to the target for display in the UI.
/// </summary>
/// <returns>
/// The value to be passed to the target dependency property.
/// </returns>
/// <param name="value">The source data being passed to the target.</param><param name="targetType">The <see cref="T:System.Type"/> of data expected by the target dependency property.</param><param name="parameter">An optional parameter to be used in the converter logic.</param><param name="culture">The culture of the conversion.</param>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var val = value as string;
if (String.IsNullOrEmpty(val)) return Application.Current.Resources["EmptyStyle"];
return Application.Current.Resources["FilledStyle"];
}
/// <summary>
/// Modifies the target data before passing it to the source object. This method is called only in <see cref="F:System.Windows.Data.BindingMode.TwoWay"/> bindings.
/// </summary>
/// <returns>
/// The value to be passed to the source object.
/// </returns>
/// <param name="value">The target data being passed to the source.</param><param name="targetType">The <see cref="T:System.Type"/> of data expected by the source object.</param><param name="parameter">An optional parameter to be used in the converter logic.</param><param name="culture">The culture of the conversion.</param>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Usage
<TextBox x:Name="locationtbx" Style="{Binding ElementName=locationtbx, Path=Text, Converter={StaticResource TextStyleConverter}}" />
Related
This question already has answers here:
Binding ConverterParameter
(3 answers)
Closed 27 days ago.
In the following xaml sample source, I am trying to bind the A property in the SampleViewModel to the B property, which is a DependencyProperty in the SampleConverter.
However, when I do this, I get a XAML bind error and the Data Context is displayed as null.
I know it is possible to get the Data Context using x:Name, but is it possible to get the Data Context without using x:Name?
<Window
x:Class="WpfApp1.BindPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Window.DataContext>
<local:SampleViewModel />
</Window.DataContext>
<StackPanel>
<StackPanel.Height>
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Mode=Self}">
<Binding.Converter>
<local:SampleConverter B="{Binding A}" />
</Binding.Converter>
</Binding>
</StackPanel.Height>
</StackPanel>
</Window>
I should mention that with RelativeSource I could not get other than myself (in this case, other than the SampleConverter).
That isn't how you use converters.
I don't know what local:SampleConverter is exactly but it has a property B
B="{Binding A}"
The binding provides the value, you may also bind a command parameter
Here's an example
<Image Name="GridImage"
Visibility="{Binding AppSettings.ShowGrid
, Converter={StaticResource BooleanToVisibilityConverter}}"
/>
This is going to set the Visibility property, the binding is to AppSetting.ShowGrid which is in the datacontext rather than converter. The BooleanToVibilityConverter is taking a bool from ShowGrid and converts it to a Visibility.
If you wanted to bind multiple properties then you can use a multiconverter with a multibinding.
<MultiBinding Converter="{ui:AllMultiplyMultiConverter}" StringFormat="{}{0:n0}">
<Binding Path="Value" ElementName="turnTime"/>
<Binding Path="MoveRate"/>
</MultiBinding>
A multiconverter receives an array object[] for those values.
Since markup extension and ivalueconverter are not dependency objects you would need to reference the parent object to use a dependency property.
You could add dependency properties to your window and reference them in a markup extension that's also a valueconverter.
Consider this value converter that's also a markup extension.
public class AddConverter : MarkupExtension, IValueConverter
{
public double ValueToAdd { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double bound = (Double)value;
return bound + ValueToAdd;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
ValueToAdd is just a property, you can't bind it.
You can get a reference in that ProvideValue to the parent dependency object. Hence window or usercontrol.
IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
That target is your control and you can grab dependency properties off that. You could (say) set ValueToAdd in there and use it in the converter. You could cast the datacontext of the targetobject and read values off properties directly.
This is a very complicated approach. I've never had the need for this myself and I would recommend multibinding and multiconverter instead.
I am quite proficient in WPF but new to the area of implementing UI validation where I need to ensure that certain values are ready for saving to a database. In my very large application I have a lot of different types of validation, which includes the simple single (TextBox requires a value or minimum characters), (Item must be selected), (At least one option must be chosen) and so on.
I have implemented the validation using INotifyDataErrorInfo and this works really well, except for a few issues that I am going around in circles with and need some guidance. In fact, this is probably more of styling question. The following is just one of my issues but if I solve this then it may solve the others so I’ll stick to this for now:
I have a set of radio button controls where one must be selected by the user but I do not want any to be selected by default, so are forced to make the choice. Until they choose one, a red border needs to be displayed around the radio buttons which are in a stack panel. Because this is something that I want to do in several places where I have a group of controls, I thought it would be good to create a Border control called ErrorBorderControl, that manages data errors using a property and then pop the items into this control. I create a DependecyProperty on this control called ValidationObject of type object, that just takes a property that can be tested to see if there is an error. This works perfectly and the red border is displayed when not selected and not when selected. Great so far. However, the ControlTemplate defined in the ErrorBorderControl bleeds to all other Border based controls in the UI, including the Border around the RadioButton controls.
I work a lot with styles and understand scope but this is very odd. The below is what I have done, although very basic as a first attempt:
User Control:
<UserControl
x:Class="Itec.Common.Wpf.CustomControls.Controls.ErrorBorderControl"
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">
<!--
Provides a border around grouped controls that needs error feedback
-->
<Border>
<!-- Style -->
<Border.Style>
<Style
TargetType="{x:Type Border}"
x:Name="TheTemplate">
<!-- Error template definition -->
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<!-- Adorner for the error visual -->
<AdornedElementPlaceholder>
<!-- Simple border around the control -->
<Border
BorderBrush="#ec7063"
BorderThickness="1"/>
</AdornedElementPlaceholder>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Border.Style>
</Border>
</UserControl>
Code Behind:
/// <summary>
/// Interaction logic for ErrorBorderControl.xaml
/// </summary>
public partial class ErrorBorderControl : UserControl
{
#region Dependency Properties
/// <summary>
/// The validation object property
/// </summary>
public static readonly DependencyProperty ValidationObjectProperty =
DependencyProperty.Register("ValidationObject", typeof(object), typeof(ErrorBorderControl),
new FrameworkPropertyMetadata(OnValidationObjectChanged));
#endregion Dependency Properties
#region Ctors
/// <summary>
/// Initializes a new instance of the <see cref="ErrorBorderControl"/> class.
/// </summary>
public ErrorBorderControl()
{
InitializeComponent();
}
#endregion Ctors
#region Public Properties
/// <summary>
/// Gets or sets the validation object.
/// </summary>
/// <value>The validation object.</value>
public object ValidationObject
{
get { return (object)GetValue(ValidationObjectProperty); }
set { SetCurrentValue(ValidationObjectProperty, value); }
}
#endregion Public Properties
#region Private Methods
/// <summary>
/// Raises when the ValidationObject property value changes
/// </summary>
/// <param name="d">The d.</param>
/// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
private static void OnValidationObjectChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ErrorBorderControl)d).ValidationObject = e.NewValue;
}
#endregion Private Methods
}
Implementation:
<!-- Owner type -->
<itc:ErrorBorderControl
Grid.Row="1"
ValidationObject="{Binding Path=RecordType, ValidatesOnNotifyDataErrors=True}">
<StackPanel
Orientation="Horizontal">
<!-- Owner -->
<itc:ItecRadioButton
Content="{DynamicResource Language.SingleLine.Owner}"
Margin="0,4,4,4"
IsChecked="{Binding Path=RecordType, Converter={itc:EnumToBooleanConverter EnumValue={x:Static recordOwners:RecordOwnerRecordType.Owner}}}"/>
<!-- FAO -->
<itc:ItecRadioButton
Content="{DynamicResource Language.SingleLine.FAO}"
Margin="0,4,4,4"
IsChecked="{Binding Path=RecordType, Converter={itc:EnumToBooleanConverter EnumValue={x:Static recordOwners:RecordOwnerRecordType.Fao}}}"/>
<!-- Account Manager -->
<itc:ItecRadioButton
Content="{DynamicResource Language.SingleLine.Account_Manager}"
Margin="0,4,4,4"
IsChecked="{Binding Path=RecordType, Converter={itc:EnumToBooleanConverter EnumValue={x:Static recordOwners:RecordOwnerRecordType.AccountManager}}}"/>
<!-- Watcher -->
<itc:ItecRadioButton
Content="{DynamicResource Language.SingleLine.Watcher}"
Margin="0,4,4,4"
IsChecked="{Binding Path=RecordType, Converter={itc:EnumToBooleanConverter EnumValue={x:Static recordOwners:RecordOwnerRecordType.Watcher}}}"/>
</StackPanel>
</itc:ErrorBorderControl>
Output:
Invalid Selection
Notice that it looks like the template, although defined inside the user control, is affecting other Border controls inside other control. Look at when I have made the selection:
Valid Selection
The controls left in red do not event take part in validation. How does a control template inside another control affect all Borders? I just don't get it. What I need to do is to define a template that I can apply to the control I want it to be applied to only, and to be able to re-use it.
In my main Window I have a MenuItem and a UserControl. I would like to disable/enable the MenuItem if one of the TextBoxes inside the UserControl is empty/not empty respectively.
Given a UserControl named ContactDetails and a TexBox called ContactNameTextBox, here's my xaml code for the MenuItem:
<MenuItem x:Name="DeleteContact"
Header="Delete Contact"
IsEnabled="{Binding ElementName=ContactDetails.ContactNameTextBox,Path=Text.Length, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
For some reason, the MenuItem always stays enabled. What am I missing?
You are binding to the length of the Text but you need a Converter from length to a bool, because IsEnabled property expects a bool.
public class NumToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value!=null && value is int )
{
var val = (int)value;
return (val==0) ? false : true;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value!=null && value is bool )
{
var val = (bool)value; return val ? 1 : 0;
}
return null;
}
}
Add a local xmlns for this and a resource.
xmlns:local="clr-namespace:YourNamespace"
and this is the reference to the converter class.
<local:NumToBoolConverter x:Key="NumToBoolConverter"/>
In your Binding section add this :
Converter={StaticResource NumToBoolConverter}
This can be your final MenuItem definition:
<MenuItem x:Name="DeleteContact"
Header="Delete Contact"
IsEnabled="{Binding ElementName=ContactDetails.ContactNameTextBox,
Path=Text.Length,
Converter={StaticResource NumToBoolConverter},
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
There are a couple of problems with your binding. The first is that you specified a two-way binding. That implies that you want to write back to the 'length' property in your textbox. Since it is readonly you can't.
Normally you should get an error for this:
A TwoWay or OneWayToSource binding cannot work on the read-only
property 'Length' of type 'System.String'.
Now strangely enough, the binding does work after that. But that is REALLY not the right way. The magic of .NET is allowing a 0 to be interpreted as 'false'. But it is not a safe binding. As Olaru said in his answer, the length property is an integer and the IsEnabled field is looking for a bool. What if you wanted to bind to the 'visibility' property?
So what is the best way to handle this then? Converters are definitely one choice, and in many cases the best choice. The advantage to converters is that they can be re-used in similar cases. We have a library full of converters that we use very often. Olaru has described how to do that, so I won't repeat what he has already said.
In some cases though, it is beneficial to know a different way. A datatrigger will allow you to do the same kind of thing as a converter. It is a one-way binding. Here is an example.
<MenuItem x:Name="DeleteContact" Header="Delete Contact">
<MenuItem.Style>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="MenuItem.IsEnabled" Value="true"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text.Length, ElementName=ContactNameTextBox}" Value="0">
<Setter Property="MenuItem.IsEnabled" Value="false"/>
</DataTrigger>
</Style.Triggers>
</Style>
</MenuItem.Style>
</MenuItem>
No code necessary!!
There are plenty of arguments about the pros and cons of converters and datatriggers. But the main thing is to know that there are more than one way to do what you are asking.
I have a datagridview and am trying to dynamically update the background color of a row depending on the result of a comparison between one column and two others. My datagridview is bound to a datatable. The three different columns in the datagridview are min, max, and present. The values in the min and max columns are static and do not change. The values in the present column for each row update dynamically.
I use a class called MinMaxTester that implements the IValueConverter interface to compare contents of cells to return a brush color.
With the solution I have implemented, I note that the background color sometimes updates. The datagridview is part of a tab item within a tab control. When the datagridview is not visible to the user, the background color will usually update. When the datagridview is visible to the user (IE the tab item within the tab control has been selected), the background color will not update.
I am wondering what I need to change in my solution so that the row background color will always update?
XAML file Code
<Window.Resources>
<local:MinMaxTester x:Key="MinMaxTester"/>
</Window.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="Present" Binding="{Binding Present}"/>
<DataGridTextColumn Header="Min" Binding="{Binding Min}"/>
<DataGridTextColumn Header="Max" Binding="{Binding Max}"/>
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="{Binding Converter={StaticResource MinMaxTester}}"/>
</Style>
<DataGrid.RowStyle>
Implementation Code
[ValueConversion(typeof(DataRowView),typeof(Brush))]
public class MinMaxTester: IValueConverter
{
public object Convert(object datagridrow, Type target, object parameter, System.Globalization.CultureInfo culture)
{
int min, max, present;
DataRowView r = datagridrow as DataRowView;
min = int.Parse(r["Min"].ToString());
max = int.Parse(r["Max"].ToString());
present = int.Parse(r["Present"].ToString());
if (present >= min && present <= max)
return Brushes.Green;
else
return Brushes.Red;
}
public object ConvertBack(object datagridrow, Type target, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException("Not using the ConvertBack function in MinMaxTester");
}
}
have you tried this ?
<Window.Resources>
<my:RowBackgroundColorConverter x:Key="rowBackgroundColorConverterResource"></my:RowBackgroundColorConverter>
</Window.Resources>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="{Binding fieldXXX, Converter={StaticResource converterXXX}}"></Setter>
</Style>
</DataGrid.RowStyle>
And the converter code:
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows;
using System.Windows.Controls;
namespace XXX.Converters
{
public class RowBackgroundColorConverter : IValueConverter
{
private readonly Color expiredColor = Colors.Red;
private readonly Color normalColor = Colors.Gray;
public object Convert(
object value,
Type targetType,
object parameter,
CultureInfo culture)
{
if (XXXXX)
return new SolidColorBrush(expiredColor);
return new SolidColorBrush(normalColor);
}
public object ConvertBack(
object value,
Type targetType,
object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
}
I'm not sure what your Present is doing and where, but this might work what you need (w/o changing your code too much)
<MultiBinding Converter="{StaticResource converter}" Mode="OneWay">
<MultiBinding.Bindings>
<Binding Path="Present" />
<Binding Path="" />
</MultiBinding.Bindings>
</MultiBinding>
...and then in your converter (which is now IMultiValueConverter - all similar just one more field) you have both values. You use 'row' to calculate.
...and you use direct binding to Present to trigger the change.
You also need to make sure that whatever 'holds' the Present - have INotifyPropertyChanged as mentioned already.
Hope it helps
You said you are binding to a DataTable, however data in a DataTable do not implement INotifyPropertyChanged, so will not raise a PropertyChange notification to tell the UI that it needs to update.
I would suggest switching to binding your DataGrid from a DataTable to an ObservableCollection<MyDataObject>, and make sure MyDataObject implements INotifyPropertyChanged
I have a bunch of textblocks in an itemscontrol... I need to know how can I underline the text in the textblock based on whether the text is available in a list in the data model..
Sounds very simple to me...but I have been googling since the past 8 hrs...
Can I use datatriggers and valueconverters for this purpose? If yes, then how can I execute the method which lies in the viewModel (the method which helps me to check whther a given a text exists in the data model list)...
Even if I go for conditional templating....how do I access the list which lies in my model (the viewmodel can fetch it...but then how do i access the viewmodel?)..
This should be a fairly easy thing to do...Am I really missing something very simple here?? :)
I am following the MVVM pattern for my application..
One way is to use a multivalueconverter which is a class that implements IMultiValueConverter. A multivalueconverter allows you to bind to several values which means that you can get a reference to both your viewmodel and the text of your TextBlock in your valueconverter.
Assuming that your viewmodel has a method called GetIsUnderlined that returns true or false indicating whether or not the text should be underlined your valueconverter can be implemented along these lines:
class UnderlineValueConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var viewmodel = values[0] as Window1ViewModel;
var text = values[1] as string;
return viewmodel.GetIsUnderlined(text) ? TextDecorations.Underline : null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
You can use this valueconverter in the following way for a TextBlock:
<Grid x:Name="grid1" >
<Grid.Resources>
<local:UnderlineValueConverter x:Key="underlineValueConverter" />
</Grid.Resources>
<TextBlock Text="Blahblah">
<TextBlock.TextDecorations>
<MultiBinding Converter="{StaticResource underlineValueConverter}">
<Binding /> <!-- Pass in the DataContext (the viewmodel) as the first parameter -->
<Binding Path="Text" RelativeSource="{RelativeSource Mode=Self}" /> <!-- Pass in the text of the TextBlock as the second parameter -->
</MultiBinding>
</TextBlock.TextDecorations>
</TextBlock>
</Grid>