Custom trigger for length of TextBox text - c#

In the trigger below, if TextBox Text value is empty, Border color will be Red.
<Style TargetType="TextBox" >
<Style.Triggers>
<Trigger Property="Text" Value="">
<Setter Property="BorderBrush" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
How can I (with trigger) do that when the length on the text is under 4 chars the border will be red?
Thanks!!

I'm pretty sure you want to implement some kind of validation.
Instead of using a trigger I would use the built in validation features of WPF.
One way of doing this is to implement the IDataErrorInfo interface in your view model (or model).
public class MainWindowViewModel : INotifyPropertyChanged, IDataErrorInfo
Error property implementation:
public string Error { get { return null; } }
The indexer's implementation:
public string this[string columnName]
{
get
{
if(columnName == "SomeRandomText")
{
if(string.IsNullOrEmpty(SomeRandomText) || SomeRandomText.Length < 4)
{
return "Text should be at least four characters long";
}
}
return null;
}
}
In XAML:
<TextBox Text="{Binding SomeRandomText, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
Result:
If you don't want to implement the IDataErrorInfo interface you can use ValidationRules.
Same thing achieved with a ValidationRule:
<Binding Path="SomeRandomText" ValidatesOnDataErrors="True" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:LengthValidationRule RequiredLength="4" />
</Binding.ValidationRules>
</Binding>
And the ValidationRule:
public class LengthValidationRule : ValidationRule
{
public int RequiredLength { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var text = (string)value;
if(string.IsNullOrEmpty(text) || text.Length < RequiredLength)
{
return new ValidationResult(false, "Text should be at least four characters long");
}
return ValidationResult.ValidResult;
}
}
If you want to have a different border you can look into the Validation.ErrorTemplate attached property.
If you really want to do this with a trigger, as others mentioned you can do it with a converter named for example LessThanConverter. It would take the Length of the Text and the other number you want to compare it with as the ConverterParameter and would return a bool.

If you really want a Style.Trigger based solution you need a converter:
The converter converts the String input to a Boolean, weather the input meets your criteria. In this example the text length must be bigger than 4.
Converter Class:
public class LengthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var text = value as string;
if (text != null) {
return text.Length > 4;
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
XAML:
<Window.Resources>
<conv:LengthConverter x:Key="converter" />
<Style TargetType="TextBox" >
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Text,
RelativeSource={RelativeSource Self},
Converter={StaticResource converter}}"
Value="False">
<Setter Property="BorderBrush" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
But a proper validation (like in Szabolcs Dézsi's answer) is probably the better approach.

Reference assemblies :
Microsoft.Expression.Interactions.dll, and
System.Windows.Interactivity.dll .
On my machine they are in :
C:\Program Files (x86)\Microsoft SDKs\Expression\Blend\.NETFramework\v4.0\Libraries
<TextBox x:Name="textBox" HorizontalAlignment="Left" Margin="96,0,0,164.04" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Bottom">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged">
<i:Interaction.Behaviors>
<ei:ConditionBehavior>
<ei:ConditionalExpression>
<ei:ComparisonCondition Operator="LessThan" RightOperand="4" LeftOperand="{Binding Text.Length, ElementName=textBox}"/>
</ei:ConditionalExpression>
</ei:ConditionBehavior>
</i:Interaction.Behaviors>
<ei:ChangePropertyAction PropertyName="BorderBrush">
<ei:ChangePropertyAction.Value>
<SolidColorBrush Color="#FFD41717"/>
</ei:ChangePropertyAction.Value>
</ei:ChangePropertyAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>

Related

WPF binding window title to a checkbox state

I have a checkbox which looks something like this (have removed many things to make it short) -
<CheckBox IsChecked="{Binding functionABC, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding Path=XYZ}"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<i:InvokeCommandAction Command="{Binding Path=XYZ}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
Now, I have to map the window title of app to 2 different values depending on checked or unchecked. I could have done it normally, but there is a trigger set already for both states, and I don't know how to work around it.
Use a Style with a DataTrigger
<!-- !!! remove the Title property from the Window declaration !!! -->
<Window
...>
<Window.Style>
<Style TargetType="Window">
<Style.Triggers>
<DataTrigger Binding="{Binding functionABC}" Value="True">
<Setter Property="Title" Value="True Title" />
</DataTrigger>
<DataTrigger Binding="{Binding functionABC}" Value="False">
<Setter Property="Title" Value="False Title" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
...
</Window>
Update
As thatguy suggested this style can be simplified by
<!-- !!! remove the Title property from the Window declaration !!! -->
<Window
...>
<Window.Style>
<Style TargetType="Window">
<Setter Property="Title" Value="False Title" />
<Style.Triggers>
<DataTrigger Binding="{Binding functionABC}" Value="True">
<Setter Property="Title" Value="True Title" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
...
</Window>
sample project on github
You can use an special IValueConverter
public class BooleanToCustomConverter : MarkupExtension, IValueConverter
{
public string? TrueValue { get; set; }
public string? FalseValue { get; set; }
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool b)
return b ? TrueValue : FalseValue;
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
and use that within your Binding
<Window
...
Title="{Binding functionABC, Mode=OneWay, Converter={local:BooleanToCustomConverter TrueValue='True Value', FalseValue='False Value'}}"
...>
...
</Window>
sample project on github

Binding the style of the control to a Viewmodel property

I am trying to create a banner with different states and colors to show messages to the user, showing a Border with a specific style and a Label with an style bases in the Border style and using DataTrigger. I have created each custom styles for each state in my App.xaml and I am trying to change the state based on a property of my ViewModel.
The problem is that the style doesn't change every time I change the property, but nevertheless if I modify some of the XAML during debugging, the style is refreshed correctly.
Maybe I am missing a NotifyPropertyChanged somewhere?
Visual example:
This is My code:
App.xaml with my defined styles and my custom converter
<Application.Resources>
<local:StyleConverter x:Key="StyleConverter" />
<Style x:Key="Banner" TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Padding" Value="10" />
<Setter Property="CornerRadius" Value="5" />
</Style>
<Style x:Key="Banner_Info" TargetType="{x:Type Border}" BasedOn="{StaticResource Banner}">
<Setter Property="Background" Value="#e3f7fc" />
<Setter Property="BorderBrush" Value="#8ed9f6" />
</Style>
<Style x:Key="Banner_Error" TargetType="{x:Type Border}" BasedOn="{StaticResource Banner}">
<Setter Property="Background" Value="#ffecec" />
<Setter Property="BorderBrush" Value="#f5aca6" />
</Style>
<Style x:Key="Banner_Success" TargetType="{x:Type Border}" BasedOn="{StaticResource Banner}">
<Setter Property="Background" Value="#e9ffd9" />
<Setter Property="BorderBrush" Value="#a6ca8a" />
</Style>
<Style x:Key="Banner_Text" TargetType="{x:Type Label}">
<Setter Property="FontWeight" Value="DemiBold"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding BorderBrush.Color, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Border}}}" Value="#8ed9f6">
<Setter Property="Foreground" Value="#31708F" />
</DataTrigger>
<DataTrigger Binding="{Binding BorderBrush.Color, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Border}}}" Value="#f5aca6">
<Setter Property="Foreground" Value="#B10009" />
</DataTrigger>
<DataTrigger Binding="{Binding BorderBrush.Color, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Border}}}" Value="#a6ca8a">
<Setter Property="Foreground" Value="#529214" />
</DataTrigger>
</Style.Triggers>
</Style>
</Application.Resources>
Styleconverter.cs
public class StyleConverter : IValueConverter {
public object Convert (object value, Type targetType, object parameter, CultureInfo culture) {
if (targetType != typeof (Style)) {
throw new InvalidOperationException ("The target must be a Style");
}
var styleProperty = parameter as string;
if (value == null || styleProperty == null) {
return null;
}
string styleValue = value.GetType ()
.GetProperty (styleProperty)
.GetValue (value, null)
.ToString ();
if (styleValue == null) {
return null;
}
Style newStyle = (Style) Application.Current.TryFindResource (styleValue);
return newStyle;
}
public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException ();
}
}
Banner.cs
public class Banner : INotifyPropertyChanged {
private string _Message;
public string Message {
get => _Message;
private set {
_Message = value;
RaisePropertyChanged (null);
}
}
private string _Style = "Banner_Success";
public string Style {
get => _Style;
private set {
_Style = value;
RaisePropertyChanged (null);
}
}
public Banner SetSuccess (string message) {
Style = "Banner_Success";
Message = message;
return this;
}
public Banner SetInfo (string message) {
Style = "Banner_Info";
Message = message;
return this;
}
public Banner SetError (string message) {
Style = "Banner_Error";
Message = message;
return this;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged (string PropertyName) {
PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (PropertyName));
}
#endregion
}
Mainwindow.xml
<Grid>
<StackPanel Orientation="Vertical" VerticalAlignment="Center">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"></ColumnDefinition>
<ColumnDefinition Width="10*"></ColumnDefinition>
<ColumnDefinition Width="2*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Border Grid.Column="1" Grid.Row="0" VerticalAlignment="Center" Style="{Binding ., Mode=TwoWay, Converter={StaticResource StyleConverter}, ConverterParameter=BannerStyle}">
<Label Grid.Column="1" Style="{StaticResource Banner_Text}" Content="{Binding BannerMessage}" />
</Border>
</Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Padding="10" Margin="10" Content="BLUE" Command="{Binding CmdChangeColor, UpdateSourceTrigger=PropertyChanged}" CommandParameter="blue" />
<Button Padding="10" Margin="10" Content="RED" Command="{Binding CmdChangeColor, UpdateSourceTrigger=PropertyChanged}" CommandParameter="red" />
<Button Padding="10" Margin="10" Content="GREEN" Command="{Binding CmdChangeColor, UpdateSourceTrigger=PropertyChanged}" CommandParameter="green" />
</StackPanel>
</StackPanel>
</Grid>
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged {
public Banner Banner { get; } = new Banner ();
public string BannerStyle => Banner.Style;
public string BannerMessage => Banner.Message;
public RelayCommand CmdChangeColor { get; }
public MainViewModel () {
Banner.SetSuccess ("This is the initial message");
CmdChangeColor = new RelayCommand (param => ChangeColor (param.ToString ()));
}
public void ChangeColor (string color) {
switch (color) {
case "blue":
Banner.SetInfo ("INFO!! This should be a banner with blue background");
break;
case "red":
Banner.SetError ("ERROR!! This should be a banner with red background");
break;
case "green":
Banner.SetSuccess ("SUCCESS!! This should be a banner with green background");
break;
}
RaisePropertyChanged (null);
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged (string PropertyName) {
PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (PropertyName));
}
#endregion
}
bind to property which has notifications, instead of entire view model ({Binding .}). This way also simplifies converter (no more reflection)
<Border Style="{Binding Path=Banner.Style, Converter={StaticResource StyleConverter}">
public class StyleConverter : IValueConverter {
public object Convert (object value, Type targetType, object parameter, CultureInfo culture) {
if (targetType != typeof (Style)) {
throw new InvalidOperationException ("The target must be a Style");
}
string styleValue = value?.ToString();
if (styleValue == null) {
return null;
}
Style newStyle = (Style) Application.Current.TryFindResource (styleValue);
return newStyle;
}
public object ConvertBack (object value, Type targetType, object parameter, CultureInfo culture) {
throw new NotImplementedException ();
}
}

Default DataTemplate for a control

I am trying to make a property editor which would show different control for different data types.
For example if the data is a bool it should be a checkbox. If it is a color it should be a color picker. If it is an int it should be a numeric up down ect.
For everything else it should be a textbox <-- this is where im struggling.
for bool I can do
<DataTemplate DataType="{x:Type mscorlib:Boolean}">
<CheckBox IsChecked="{Binding Path=.}"/>
</DataTemplate>
and this works perfectly.
But I can't figure out how to make the default textbox case happen.
<DataTemplate>
<TextBlock >
</DataTemplate>
would give me IDictionary must have a Key attribute error
If I adds a key to the template it won't be used unless I explicitly do something like DataTemplate="..."
I cant seem to find a way for the given template to target more than one type either. Which is forcing me to copy paste the template over and over for each type I wish to support.
Is there a better way to do it??
Most professional way of doing this to use templateselector.
Use the IntegerUpDown control in the xtended wpf toolkit for numeric
up and down
Use the colorpicker control of xtended wpf toolkit for color picker
Include this in xaml
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
<DataTemplate x:Key="TEMPLATE">
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
<DataTemplate x:Key="BOOLEANTEMPLATE">
<CheckBox IsChecked="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
<DataTemplate x:Key="numericTemplate">
<xctk:IntegerUpDown Name="myUpDownControl" />
</DataTemplate>
<DataTemplate x:Key="ColorTemplate">
<xctk:ColorPicker></xctk:ColorPicker>
</DataTemplate>
<me:DynamicDataTemplateSelector x:Key="datagridDynamictemplateselector" BooleanTemplate="{StaticResource BOOLEANTEMPLATE}"
ColorTemplate ="{StaticResource ColorTemplate}"
NumericTemplate ="{StaticResource numericTemplate}"
TextBoxTemplate="{StaticResource TEMPLATE}" />
Here is the class which override datatemplateselector class
public class DynamicDataTemplateSelector : DataTemplateSelector
{
public DataTemplate TextBoxTemplate{get;set;}
public DataTemplate BooleanTemplate{get;set;}
public DataTemplate NumericTemplate { get; set; }
public DataTemplate ColorTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
DataTemplate dataTemplate = TextBoxTemplate;
if(item!=null)
{
Type dataTypeOfValue = item.GetType();
if(dataTypeOfValue==typeof(int))
{
dataTemplate = NumericTemplate;
}
else if(dataTypeOfValue==typeof(Color))
{
dataTemplate = ColorTemplate;
}
else if (dataTypeOfValue == typeof(Boolean) || dataTypeOfValue == typeof(bool))
{
dataTemplate = BooleanTemplate;
}
}
return dataTemplate;
}
I solved the problem by using xaml style + converter.
<ContentPresenter Content="{Binding MyValue}"
<ContentPresenter.Style>
<Style TargetType="ContentPresenter">
<Style.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="Dead beef"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=., Converter={StaticResource ToTypeConverter}}" Value="{x:Type mscorlib:Boolean}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=.}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
converter
public class ToTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value == null) ? null : value.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The idea is to put the default template inside style's ContentTemplate and only modify the special ones using style trigger.

Conditional text binding XAML

I have 3 properties that I'm trying to bind to a Textblock in XAML. One is a conditional and the other two are the strings that I want to display depending on that conditional.
<TextBlock Text="{Binding TrueText}" Style="{StaticResource styleSimpleText}" Visibility="{Binding ShowTrueText, Converter={StaticResource boolToVisibilityConverter}}"/>
<TextBlock Text="{Binding FalseText}" Style="{StaticResource styleSimpleText}" Visibility="{Binding ShowTrueText, Converter={StaticResource invertedBoolToVisibilityConverter}}"/>
This works, but now the textblocks have to have different names. Can I turn this into one TextBlock with the conditional inside of it?
You could achieve that with a Style and a DataTrigger:
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding FalseText}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ShowTrueText}" Value="True">
<Setter Property="Text" Value="{Binding TrueText}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
An alternative would be to use a MultiBinding with a multi-value converter:
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TextConverter}">
<Binding Path="TrueText"/>
<Binding Path="FalseText"/>
<Binding Path="ShowTrueText"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
The converter would look like this:
public class TextConverter : IMultiValueConverter
{
public object Convert(
object[] values, Type targetType, object parameter, CultureInfo culture)
{
var trueText = (string)values[0];
var falseText = (string)values[1];
var showTrueText = (bool)values[2];
return showTrueText ? trueText : falseText;
}
public object[] ConvertBack(
object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
The way we do this type of thing for MVVM is to create a property in your viewmodel for this. This allows for you to have unit testing for your condition on the viewmodel.
The property in your viewmodel will be the string value that the TextBlock is bound to. The viewmodel at some point will determine the value of that string based on the conditional logic that you need.
Yes, you can, just wrap them in a TextBlock as follows:
<TextBlock x:name="myTextBlock" Style="{StaticResource styleSimpleText}">
<TextBlock Text="{Binding TrueText}" Visibility="{Binding ShowTrueText, Converter={StaticResource boolToVisibilityConverter}}"/>
<TextBlock Text="{Binding FalseText}" Visibility="{Binding ShowTrueText, Converter={StaticResource invertedBoolToVisibilityConverter}}"/>
</TextBlock>
However, I think the best answer is the one provided by Clemens (using a DataTrigger).
You could set it up in your viewmodel and let it determine which text to show.
private static readonly string TRUETEXT = "This is the text to show when true";
private static readonly string FALSETEXT = "This is the text to show when false";
private bool _myBooleanProperty;
public bool MyBooleanProperty
{
get { return _myBooleanProperty; }
set
{
if (_myBooleanProperty != value)
{
_myBooleanProperty = value;
OnPropertyChanged("MyBooleanProperty");
OnPropertyChanged("ResultText");
}
}
}
public string ResultText
{
get
{
return MyBooleanProperty ? TRUETEXT : FALSETEXT;
}
}
Then you bind to it with just a single textblock. No visibility converter needed.
If there is a state where no text should show, you could work that in as well.
<TextBlock Text="{Binding ResultText}" Style="{StaticResource styleSimpleText}" />
In my opinion, the best solution to this problem would be a new string property in your view model which returns either TrueText or FalseText depending on the conditional. With such a property, you can just use a plain binding.
public string TheNewProperty
{
get
{
return ShowTrueText ? TrueText : FalseText;
}
}
<TextBlock Text="{Binding TheNewProperty}" Style="{StaticResource styleSimpleText}"/>

Proper DataGrid search from TextBox in WPF using MVVM

I am new to the MVVM pattern, and a little confused on when to use Code Behind. I have a very simple form right now, that includes one TextBox, and one DataGrid. What I would like is to be able to have the DataGrid change its selected item based on the TextBox.
I have done this in Code Behind and it works fine using the following code:
private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
for (int i = 0; i < dataGrid1.Items.Count; i++)
{
string cellContent = dtReferral.Rows[i][0].ToString();
try
{
if (cellContent != null && cellContent.Substring(0, textBox1.Text.Length).Equals(textBox1.Text))
{
object item = dataGrid1.Items[i];
dataGrid1.SelectedItem = item;
dataGrid1.ScrollIntoView(item);
//row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
break;
}
}
catch { }
}
}
Now, I just want to highlight the Item in the Datagrid that starts with text in textbox, and allow the user to press a button to edit selected item.
Is it okay to have this logic in the Code Behind file? Or would I need to do this through some sort of binding? If I should do this through the View Model with Binding, any direction would be appreciated. Thank you.
If you only want to highlight the cells with the text from the TextBox you could make an AttatchedProperty for the DataGrid to accept your search value from the TextBox and create another AttatchedProperty for the Cell to indicate a match that you can usee to set properties in the Cell style. Then we create a IMultiValueConverter to check the Cell value for a match to the search Text.
This way its reusable on other projects as you only need the AttachedProperties and Converter
Bind the AttachedProperty SearchValue to your TextBox Text property.
<DataGrid local:DataGridTextSearch.SearchValue="{Binding ElementName=SearchBox, Path=Text, UpdateSourceTrigger=PropertyChanged}"
Then create a Style for DataGridCell and create a Setter for the AttachedProperty IsTextMatch using the IMultiValueConverter to return if the cells text matches the SearchValue
<Setter Property="local:DataGridTextSearch.IsTextMatch">
<Setter.Value>
<MultiBinding Converter="{StaticResource SearchValueConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="Content.Text" />
<Binding RelativeSource="{RelativeSource Self}" Path="(local:DataGridTextSearch.SearchValue)" />
</MultiBinding>
</Setter.Value>
</Setter>
Then we can use the Cells attached IsTextMatch property to set a highlight using a Trigger
<Style.Triggers>
<Trigger Property="local:DataGridTextSearch.IsTextMatch" Value="True">
<Setter Property="Background" Value="Orange" />
</Trigger>
</Style.Triggers>
Here is a working example showing my rambilings :)
Code:
namespace WpfApplication17
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
for (int i = 0; i < 20; i++)
{
TestData.Add(new TestClass { MyProperty = GetRandomText(), MyProperty2 = GetRandomText(), MyProperty3 = GetRandomText() });
}
}
private string GetRandomText()
{
return System.IO.Path.GetFileNameWithoutExtension(System.IO.Path.GetRandomFileName());
}
private ObservableCollection<TestClass> _testData = new ObservableCollection<TestClass>();
public ObservableCollection<TestClass> TestData
{
get { return _testData; }
set { _testData = value; }
}
}
public class TestClass
{
public string MyProperty { get; set; }
public string MyProperty2 { get; set; }
public string MyProperty3 { get; set; }
}
public static class DataGridTextSearch
{
// Using a DependencyProperty as the backing store for SearchValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SearchValueProperty =
DependencyProperty.RegisterAttached("SearchValue", typeof(string), typeof(DataGridTextSearch),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.Inherits));
public static string GetSearchValue(DependencyObject obj)
{
return (string)obj.GetValue(SearchValueProperty);
}
public static void SetSearchValue(DependencyObject obj, string value)
{
obj.SetValue(SearchValueProperty, value);
}
// Using a DependencyProperty as the backing store for IsTextMatch. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsTextMatchProperty =
DependencyProperty.RegisterAttached("IsTextMatch", typeof(bool), typeof(DataGridTextSearch), new UIPropertyMetadata(false));
public static bool GetIsTextMatch(DependencyObject obj)
{
return (bool)obj.GetValue(IsTextMatchProperty);
}
public static void SetIsTextMatch(DependencyObject obj, bool value)
{
obj.SetValue(IsTextMatchProperty, value);
}
}
public class SearchValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string cellText = values[0] == null ? string.Empty : values[0].ToString();
string searchText = values[1] as string;
if (!string.IsNullOrEmpty(searchText) && !string.IsNullOrEmpty(cellText))
{
return cellText.ToLower().StartsWith(searchText.ToLower());
}
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
}
Xaml:
<Window x:Class="WpfApplication17.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication17"
Title="MainWindow" Height="350" Width="525" Name="UI">
<StackPanel DataContext="{Binding ElementName=UI}">
<TextBox Name="SearchBox" />
<DataGrid x:Name="grid" local:DataGridTextSearch.SearchValue="{Binding ElementName=SearchBox, Path=Text, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding TestData}" >
<DataGrid.Resources>
<local:SearchValueConverter x:Key="SearchValueConverter" />
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="local:DataGridTextSearch.IsTextMatch">
<Setter.Value>
<MultiBinding Converter="{StaticResource SearchValueConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="Content.Text" />
<Binding RelativeSource="{RelativeSource Self}" Path="(local:DataGridTextSearch.SearchValue)" />
</MultiBinding>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="local:DataGridTextSearch.IsTextMatch" Value="True">
<Setter Property="Background" Value="Orange" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
</StackPanel>
</Window>
Result:
Edit:
If you just want to select the row based on a single Column you can modify quite easily :).
Override the Style of DataGridRow instead of DataGridCell.
<Style TargetType="{x:Type DataGridRow}">
First pass in the property you want into the IMultiValueConverter this should be your DataContext
<MultiBinding Converter="{StaticResource SearchValueConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="DataContext.MyProperty" />
<Binding RelativeSource="{RelativeSource Self}" Path="(local:DataGridTextSearch.SearchValue)" />
</MultiBinding>
Then change the Trigger to set IsSelected on the Row
<Style.Triggers>
<Trigger Property="local:DataGridTextSearch.IsTextMatch" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
Should look like this:
<DataGrid x:Name="grid" local:DataGridTextSearch.SearchValue="{Binding ElementName=SearchBox, Path=Text, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding TestData}" >
<DataGrid.Resources>
<local:SearchValueConverter x:Key="SearchValueConverter" />
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="local:DataGridTextSearch.IsTextMatch">
<Setter.Value>
<MultiBinding Converter="{StaticResource SearchValueConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="DataContext.MyProperty" />
<Binding RelativeSource="{RelativeSource Self}" Path="(local:DataGridTextSearch.SearchValue)" />
</MultiBinding>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="local:DataGridTextSearch.IsTextMatch" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
Result:
I have been using MVVM for quite a while now, and I still prefer using it as a guideline rather than a strict practice, partly because it isn't always practical to do everything in MVVM pattern exactly, and even more so if you are not too familiar with it.I would suggest just playing around with it until you manage to find a form of MVVM that suits you.
I don't believe it is taboo to have Code in the Code Behind of the MVVM if the code is UI related. ScrollIntoView isn't a Bindable property so if you want to bind to it you will have to create a dependency Property to indirectly handle the binding. As for setting the selected item you could do it through something like:
View:
<TextBox Height="23" Text={Binding Path=Selected, UpdateSourceTrigger=PropertyChanged} HorizontalAlignment="Left" Margin="90,147,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" />
<DataGrid AutoGenerateColumns="True"
ItemsSource="{Binding Path=ItemList}"
SelectedItem="{Binding Path=Selected}" >
</DataGrid>
ViewModel:
private string _selected = "";
public string Selected
{
get{ return _selected; }
set
{
if(_selected == value) return;
_selected = value;
base.OnPropertyChanged("Selected");
}
}

Categories