ListBox doesn't show any item - c#

I have created a ListBox with custom ListBoxItem Template, had bound everything and it worked. When I was still working on my project. I runned the program and that ListBox weren't showing items anymore. Here is my code:
This is ListBox which doesn't show any item:
<ListBox
x:Name="LB_SongList"
HorizontalAlignment="Left"
Height="498"
Margin="0,30,0,0"
VerticalAlignment="Top"
Width="319"
Background="{x:Null}"
BorderBrush="{x:Null}"
Drop="LB_SongList_Drop"
PreviewMouseMove="LB_SongList_PreviewMouseMove"
PreviewMouseDoubleClick="LB_SongList_PreviewMouseDoubleClick"
SelectionChanged="LB_SongList_SelectionChanged"
AllowDrop="True"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Visible"
SelectionMode="Multiple"
ItemTemplate="{DynamicResource SongTemplate}"/>
Here is my ListBoxItem Template:
<DataTemplate x:Key="SongTemplate">
<Grid Width="302" Height="35">
<Label Content="{Binding SongName}" HorizontalAlignment="Stretch" Margin="4" VerticalAlignment="Stretch" Width="Auto" Height="Auto" Padding="2,0,0,0" VerticalContentAlignment="Center"/>
<Border BorderBrush="#B26A6A6A" BorderThickness="4" HorizontalAlignment="Stretch" Height="35" VerticalAlignment="Top" Width="Auto">
<Border.Style>
<Style>
<Style.Triggers>
<DataTrigger
Binding="{Binding Path=IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"
Value="False">
<Setter
Property="Border.Visibility"
Value="Collapsed"
/>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
</Border>
</Grid>
</DataTemplate>
And the code:
public partial class MenuHolder : UserControl
{
private List<string> List_SongList;
public List<string> L_SongList
{
get { return List_SongList; }
set
{
_File.ListToObservableCollection_Song(value, O_SongList);
List_SongList = value;
}
}
public List<string> L_PlayLists
{
get
{
Settings.Default.Save();
return Settings.Default.L_PlayLists;
}
set
{
Settings.Default.L_PlayLists = value;
Settings.Default.Save();
}
}
public ObservableCollection<Song> O_SongList = new ObservableCollection<Song>();
public ObservableCollection<string> O_PlayList = new ObservableCollection<string>();
public MenuHolder()
{
InitializeComponent();
LB_SongList.ItemsSource = O_SongList;
LB_PlayList.ItemsSource = O_PlayList;
List<string> temp = new List<string>();
temp.Add("TestSong");
L_SongList = temp;
}
}
public class Song : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string _songName;
string _songLengh;
public Song(){}
public Song(String _name){ SongName = _name; }
public Song(String _name, String _lengh) { SongName = _name; SongLengh = _lengh; }
public string SongName
{
get { return _songName; }
set { _songName = value; RaisePropertyChanged("SongName"); }
}
public string SongLengh
{
get { return _songLengh; }
set { _songLengh = value; RaisePropertyChanged("SongLengh"); }
}
private void RaisePropertyChanged(string propertyName)
{
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And the ListToObservableCollection_Song
public void ListToObservableCollection_Song(List<string> _list, ObservableCollection<Music_Player.Menus.MainMenuObjects.Song> _collection)
{
_collection.Clear();
foreach (string _path in _list)
{
_collection.Add(new Menus.MainMenuObjects.Song(GetSongNameFromPath(_path)));
}
}
The worst thing is that it was working before and in the old version of my program it is still working. Still thinking why it is doing that.

Related

Unable to set Custom TreeView's Selected Item in Treeview programmtically

I created a WPF application where I create a list of items to be executed, as a Treeview. On a click event, I parse the ObservableCollection items one by one. This observableCollection is set as the DataContext for the treeview. When running the tests, I want to highlight the current running item in the Treeview.
I have the implemented following code, but the highlighting on the Treeview (visuallY) does not seem to happen. I checked that the "IsSelected" property does get set/unset as programmed.
I am not sure were I went wrong. Could you point out where the mistake is.
I have this class used as a DataContext to the TreeView (named mainTree).
class mytreefile : INotifyPropertyChanged
{
private string _name { get; set; }
public ObservableCollection <mytreefile> children { get; set; }
bool? _isSelected = false;
public bool? IsSelected
{
get { return _isSelected; }
set { SetIsSelected(value); }
}
void SetIsSelected(bool? val)
{
_isSelected = val;
}
public mytreefile(string value)
{
_name = value;
children = new ObservableCollection<mytreefile>();
}
void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
The XAML file is
<Grid.Resources>
<ResourceDictionary>
<HierarchicalDataTemplate x:Key="tvTemplate" ItemsSource="{Binding children, Mode=TwoWay}">
<StackPanel Orientation="Horizontal">
<ContentPresenter Content="{Binding _name, Mode=TwoWay}" Margin="2,0" />
</StackPanel>
</HierarchicalDataTemplate>
</ResourceDictionary>
</Grid.Resources>
<TreeView x:Name="mainTree" Grid.Row="0" Grid.Column="0" Grid.RowSpan="4" Background="WhiteSmoke"
Height="Auto" Width="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Margin="1,0,2,0" SelectedItemChanged="mainTree_SelectedItemChanged"
ItemTemplate="{StaticResource tvTemplate}"
ItemsSource="{Binding}" DataContext="{Binding nodes}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
<Trigger Property="IsSelected" Value="False">
<Setter Property="FontWeight" Value="Normal" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
And my MainWindow code is:
public partial class MainWindow : Window
{
ObservableCollection<mytreefile> nodes = new ObservableCollection<mytreefile>();
mytreefile mtf = null;
Thread thThread = null;
int gnCount = 0;
private void LoadTree ()
{
mytreefile tf1 = new mytreefile("Group1");
nodes.Add(tf1);
mytreefile subtf1 = new mytreefile("Sub Item 1");
mytreefile subtf2 = new mytreefile("Sub Item 2");
mytreefile subtf3 = new mytreefile("Sub Item 3");
mytreefile subtf4 = new mytreefile("Sub Item 4");
tf1.children.Add(subtf1); tf1.children.Add(subtf2); tf1.children.Add(subtf3); tf1.children.Add(subtf4);
maintree.DataContext = nodes;
}
private void OnButton1_click()
{
mtf = nodes.ElementAt(0);
gnCount = 0;
thThread = new Thread(new ThreadStart(this.myThread));
thThread.Start();
}
public void myThread ()
{
for (int i = 0; i < 3; i++)
{
Thread.Sleep(1000);
this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Send,
new Action(() => SetTreeItem(i)));
}
}
public void SetTreeItem(int i)
{
if (gnCount > 0) {
mytreefile mtreeitem = mtf.children.ElementAt(gnCount-1);
mtreeitem.IsSelected = false;
}
mytreefile mtreeitem = mtf.children.ElementAt(gnCount++);
mtreeitem.IsSelected = true;
}
}
The problem was with the "mytreefile" class.
The below class works fine. The way the "IsSelected" implementation was done made the difference. Posting the code for reference.
class mytreefile : INotifyPropertyChanged
{
private string _name { get; set; }
public ObservableCollection <mytreefile> children { get; set; }
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != this._isSelected)
{
this._isSelected = value;
NotifyPropertyChanged("IsSelected");
}
}
}
public mytreefile(string value)
{
_name = value;
children = new ObservableCollection<mytreefile>();
}
void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}

Binding in DataTrigger seems not working

I have a ComboBox which should have a text when nothing is selected. This seems like a straight forward problem, and has many answers on the net, but unfortunately, it is not working for me. I think, the reason is, that I don't want to show a static text, but rather a bound text.
My minimal not working example looks like this:
public class Model
{
public string Name { get; set; }
public SubModel SelectedItem { get; set; }
public List<SubModel> Items { get; set; }
}
public class SubModel
{
public string Description { get; set; }
}
and the MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var selectedSubModel = new SubModel { Description = "SubModel5" };
var model1 = new Model
{
Name = "Model1",
Items = new List<SubModel>
{
new SubModel { Description = "SubModel1" },
new SubModel { Description = "SubModel2" },
new SubModel { Description = "SubModel3" }
}
};
var model2 = new Model
{
Name = "Model2",
SelectedItem = selectedSubModel,
Items = new List<SubModel>
{
new SubModel { Description = "SubModel4" },
selectedSubModel,
new SubModel { Description = "SubModel6" }
}
};
var model3 = new Model
{
Name = "Model3",
Items = new List<SubModel>
{
new SubModel { Description = "SubModel7" },
new SubModel { Description = "SubModel8" },
new SubModel { Description = "SubModel9" }
}
};
_itemsControl.Items.Add(model1);
_itemsControl.Items.Add(model2);
_itemsControl.Items.Add(model3);
}
}
with xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<ItemsControl x:Name="_itemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="WpfApplication1:Model">
<ComboBox ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem}" Value="{x:Null}">
<Setter Property="Background">
<Setter.Value>
<VisualBrush>
<VisualBrush.Visual>
<TextBlock Text="{Binding Name}"/>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
This gives the follwing:
But it should look similar to this:
please first of all take in your mind facts provided in the next sentence - you can only select items provided by ComboBox ItemsSource. Thus since the Name property values (Model1, Model2, Model3 etc.) are not in your collection they can't be selected you will see the empty selection Instead. I can suggest you the next solution the combination of data context proxy and wpf behavior.
Xaml code
<Window x:Class="ComboBoxWhenNoAnySelectedHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:comboBoxWhenNoAnySelectedHelpAttempt="clr-namespace:ComboBoxWhenNoAnySelectedHelpAttempt"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl x:Name="_itemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="comboBoxWhenNoAnySelectedHelpAttempt:Model">
<ComboBox x:Name="ComboBox"
SelectedItem="{Binding SelectedItem, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.Resources>
<!--the next object is a proxy that able to provide combo data context each time it requested-->
<comboBoxWhenNoAnySelectedHelpAttempt:FreezableProxyClass x:Key="FreezableProxyClass" ProxiedDataContext="{Binding ElementName=ComboBox, Path=DataContext }"></comboBoxWhenNoAnySelectedHelpAttempt:FreezableProxyClass>
</ComboBox.Resources>
<ComboBox.ItemsSource>
<CompositeCollection>
<!--the next object is a collapsed combo box that can be selected in code-->
<!--keep im mind, since this object is not a SubModel we get the binding expression in output window-->
<ComboBoxItem IsEnabled="False" Visibility="Collapsed" Foreground="Black" Content="{Binding Source={StaticResource FreezableProxyClass},
Path=ProxiedDataContext.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></ComboBoxItem>
<CollectionContainer Collection="{Binding Source={StaticResource FreezableProxyClass},
Path=ProxiedDataContext.Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</CompositeCollection>
</ComboBox.ItemsSource>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
<i:Interaction.Behaviors>
<!--next behavior helps to select a zero index (Model.Name collapsed) item from source when selected item is not SubModel-->
<comboBoxWhenNoAnySelectedHelpAttempt:ComboBoxLoadingBehavior/>
</i:Interaction.Behaviors>
</ComboBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Code Behind with Proxy Code
public class FreezableProxyClass : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new FreezableProxyClass();
}
public static readonly DependencyProperty ProxiedDataContextProperty = DependencyProperty.Register(
"ProxiedDataContext", typeof(object), typeof(FreezableProxyClass), new PropertyMetadata(default(object)));
public object ProxiedDataContext
{
get { return (object)GetValue(ProxiedDataContextProperty); }
set { SetValue(ProxiedDataContextProperty, value); }
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var selectedSubModel = new SubModel { Description = "SubModel5" };
var model1 = new Model
{
Name = "Model1",
Items = new ObservableCollection<SubModel>
{
new SubModel { Description = "SubModel1" },
new SubModel { Description = "SubModel2" },
new SubModel { Description = "SubModel3" }
}
};
var model2 = new Model
{
Name = "Model2",
SelectedItem = selectedSubModel,
Items = new ObservableCollection<SubModel>
{
new SubModel { Description = "SubModel4" },
selectedSubModel,
new SubModel { Description = "SubModel6" }
}
};
var model3 = new Model
{
Name = "Model3",
Items = new ObservableCollection<SubModel>
{
new SubModel { Description = "SubModel7" },
new SubModel { Description = "SubModel8" },
new SubModel { Description = "SubModel9" }
}
};
_itemsControl.Items.Add(model1);
_itemsControl.Items.Add(model2);
_itemsControl.Items.Add(model3);
}
}
public class Model:BaseObservableObject
{
private string _name;
private SubModel _selectedItem;
private ObservableCollection<SubModel> _items;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public SubModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged();
}
}
public ObservableCollection<SubModel> Items
{
get { return _items; }
set
{
_items = value;
OnPropertyChanged();
}
}
}
public class SubModel:BaseObservableObject
{
private string _description;
public string Description
{
get { return _description; }
set
{
_description = value;
OnPropertyChanged();
}
}
}
BaseObservableObject code (simple implementation for INotifyPropertyChanged )
/// <summary>
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
WPF Behavior Code
public class ComboBoxLoadingBehavior:Behavior<ComboBox>
{
private bool _unLoaded;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObjectOnLoaded;
AssociatedObject.LayoutUpdated += AssociatedObjectOnLayoutUpdated;
AssociatedObject.Unloaded += AssociatedObjectOnUnloaded;
}
private void AssociatedObjectOnUnloaded(object sender, RoutedEventArgs routedEventArgs)
{
_unLoaded = true;
UnsubscribeAll();
}
private void UnsubscribeAll()
{
AssociatedObject.Loaded -= AssociatedObjectOnLoaded;
AssociatedObject.LayoutUpdated -= AssociatedObjectOnLayoutUpdated;
AssociatedObject.Unloaded -= AssociatedObjectOnUnloaded;
}
private void AssociatedObjectOnLayoutUpdated(object sender, EventArgs eventArgs)
{
UpdateSelectionState(sender);
}
private static void UpdateSelectionState(object sender)
{
var combo = sender as ComboBox;
if (combo == null) return;
var selectedItem = combo.SelectedItem as SubModel;
if (selectedItem == null)
{
combo.SelectedIndex = 0;
}
}
private void AssociatedObjectOnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
_unLoaded = false;
UpdateSelectionState(sender);
}
protected override void OnDetaching()
{
base.OnDetaching();
if(_unLoaded) return;
UnsubscribeAll();
}
}
This is a working complete solution for you problem, just copy/past and use it as a starting point for your farther research. I'll glad to help if you will have any problems with the code.
Regards.
I've found 2 possible solutions:
Change ComboBox template
Edit standard combobox template by right button click on combobox in designer and select Edit Template -> Edit a Copy...
After that change ContentPresenter with a custom converter:
XAML
<ContentPresenter ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" IsHitTestVisible="false" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<ContentPresenter.Content>
<MultiBinding Converter="{local:ComboboxEmptyValueConverter}">
<Binding Path="SelectionBoxItem" RelativeSource="{RelativeSource Mode=TemplatedParent}" />
<Binding Mode="OneWay" Path="DataContext" RelativeSource="{RelativeSource Mode=TemplatedParent}" />
</MultiBinding>
</ContentPresenter.Content>
</ContentPresenter>
C#
class ComboboxEmptyValueConverterExtension : MarkupExtension, IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string stringValue = values[0] as string;
var dataContext = values[1] as Model;
return (stringValue != null && String.IsNullOrEmpty(stringValue)) ? dataContext?.Name : values[0];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new object[] { value, null };
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
Set ComboBox to IsEditable & IsReadOnly and change Text
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="textBlock" Text="{Binding Description}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem}" Value="{x:Null}">
<Setter Property="IsEditable" Value="True" />
<Setter Property="IsReadOnly" Value="True" />
<Setter Property="Text" Value="{Binding Name}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
The answer is, to put the visual brush in the resources of the combobox:
<DataTemplate DataType="WpfApplication1:Model">
<ComboBox ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Resources>
<VisualBrush x:Key="_myBrush">
<VisualBrush.Visual>
<TextBlock Text="{Binding Name}"/>
</VisualBrush.Visual>
</VisualBrush>
</ComboBox.Resources>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem}" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource _myBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</DataTemplate>
Then, together with the rest of the code, it works like expected.

Bind Command to ContextMenu items with HierarchicalDataTemplate

I have a Button which shows a ContextMenu look like
Here the XAML:
<Window.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
<Style x:Key="MenuItemStyle" TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Data.OnSelected, Source={StaticResource proxy}}" />
</Style>
<Button Name="ButtonMenu_Export"
Click="ButtonMenu_Export_Click"
Visibility="{Binding ButtonExportEnabled,
Converter={StaticResource VisibilityConverter}}">
<StackPanel Orientation="Vertical">
<Image Source="...." />
<TextBlock Width="70" TextAlignment="Center" Text="Export" />
</StackPanel>
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding ExportMenuItems}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemContainerStyle="{StaticResource MenuItemStyle}">
<ContentPresenter Content="{Binding Text}" RecognizesAccessKey="True" />
<HierarchicalDataTemplate.ItemsSource>
<Binding Path="SubItems" />
</HierarchicalDataTemplate.ItemsSource>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</Button.ContextMenu>
</Button>
The menu is created at runtime using this List (as in this article)
public System.Collections.Generic.List<MenuItem> ExportMenuItems
{
get { return _menuService.GetParentMenuItems(); }
}
Now, what I cannot do is bind the items to the OnSelected command of MenuItem class.
The class which defines the menu is:
public class MenuItem
{
private string name;
private string text;
private int menuId;
private ICommand onSelected;
private MenuItem parent;
private ObservableCollection<MenuItem> subItems;
public MenuItem(string name, string text, int MenuId)
{
this.menuId = MenuId;
this.name = name;
this.text = text;
this.subItems = new ObservableCollection<MenuItem>();
}
public string Name { get { return this.name; } }
public string Text { get { return this.text; } }
public MenuItem Parent { get { return this.parent; } set { this.parent = value; } }
public ICommand OnSelected
{
get
{
if (this.onSelected == null)
this.onSelected = new MenuCommand(this.ItemSelected, this.ItemCanBeSelected, menuId);
return this.onSelected;
}
}
public ObservableCollection<MenuItem> SubItems
{
get { return this.subItems; }
}
}
I created a proxy class as in this article to made DataContext visible to HierarchicalDataTemplate content but maybe I misunderstood something:
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
Where I'm wrong?
Change your command binding
Command="{Binding Data.OnSelectedd,Source={StaticResource proxy}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MenuItem}"

Setting datagrid's highlighted cell in xaml

I have a DataGrid and a ListView in my application. The ListView provides information about the datagrid's selected item. I put a HyperLink in that ListView that should change the datagrid's selected item to the "parent" of the currently selected item.
My approach is to set SelectedItemin my code behind. Everything works quite well but the datagrid doesn't highlight the new selected item. But I can clearly see it is selected because off its gray background color. Is it possible to set the highlighted cell programmatically?
<ListView>
<TextBlock Text="{Binding SelectedParameter.Definition.Name, StringFormat=Name: {0:C}}"
TextWrapping="Wrap"/>
<TextBlock Text="{Binding SelectedParameter.Definition.Type, StringFormat=Datentyp: {0:C}}"
TextWrapping="Wrap"/>
TextWrapping="Wrap"/>
<Hyperlink Command="{Binding GoToMasterParameterCommand}">
Masterparameter
</Hyperlink>
</ListView>
<DataGrid Name="m_DataGrid"
ItemsSource="{Binding Path=Parameters}"
SelectedItem="{Binding SelectedParameter}"
SelectionMode="Single"
AutoGenerateColumns="False"
TargetUpdated="m_ParameterDataGrid_TargetUpdated">
<DataGrid.Columns>
<DataGridTextColumn
Header="ID"
Binding="{Binding Id}"
IsReadOnly="True"/>
<DataGridTextColumn
Header="Value"
Binding="{Binding Value.CurrentInternalValue, NotifyOnTargetUpdated=True}"
</DataGrid.Columns>
</DataGrid>
internal void GoToMasterParameter()
{
string parentId = GetParentId(this.SelectedParameter);
this.SelectedParameter = this.Parameters.Single(item => item.Id == parentId);
}
The problem you have is that the selected rows/cells are not focused as the focus is still in your listView item. What you could do is style the DataGridCell element in xaml. Here is a small piece of code that demonstrate this:
<Window.Resources>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<DataGrid ItemsSource="{Binding Tests}"
SelectedItem="{Binding GridSelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding SelectedGridIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</DataGrid>
<Button Command="{Binding ChangeSelectedItemCommand}"
Content="Change Grid Selected item"
Grid.Column="1"
VerticalAlignment="Top"/>
</Grid>
and here the viewModel part:
public class MainWindowViewModel : INotifyPropertyChanged
{
#region Private members
private List<TestClass> _tests;
private TestClass _gridSelectedItem;
private ICommand _changeSelectedItemCommand;
private int _selectedGridIndex;
#endregion
#region Constructor
public MainWindowViewModel()
{
Tests = new List<TestClass>();
for (int i = 0; i < 25; i++)
{
TestClass testClass= new TestClass {Name = "Name " + i, Title = "Title" + i};
Tests.Add(testClass);
}
}
#endregion
#region Public properties
public List<TestClass> Tests
{
get { return _tests; }
set
{
_tests = value;
OnPropertyChanged("Tests");
}
}
public TestClass GridSelectedItem
{
get { return _gridSelectedItem; }
set
{
_gridSelectedItem = value;
OnPropertyChanged("GridSelectedItem");
}
}
public int SelectedGridIndex
{
get { return _selectedGridIndex; }
set
{
_selectedGridIndex = value;
OnPropertyChanged("SelectedGridIndex");
}
}
#endregion
public ICommand ChangeSelectedItemCommand
{
get { return _changeSelectedItemCommand ?? (_changeSelectedItemCommand = new SimpleCommand(p => ChangeSelectedGridItem())); }
}
private void ChangeSelectedGridItem()
{
SelectedGridIndex++;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
the demo objects class:
public class TestClass
{
public string Title { get; set; }
public string Name { get; set; }
}
and some command class:
public class SimpleCommand : ICommand
{
private readonly Predicate<object> _canExecuteDelegate;
private readonly Action<object> _executeDelegate;
#region Constructors
public SimpleCommand(Action<object> execute)
: this(execute, null)
{
}
public SimpleCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_executeDelegate = execute;
_canExecuteDelegate = canExecute;
}
#endregion // Constructors
#region ICommand Members
public virtual bool CanExecute(object parameter)
{
return _canExecuteDelegate == null || _canExecuteDelegate(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_executeDelegate(parameter);
}
#endregion
}
Make sure to add the DataContext of the View so that it knows about your ViewModel:
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
I hope this helps you obtain the desired effect.

Implement Validation for WPF TextBoxes

I have 3 TextBoxes (Id1,Name and Salary). Id and Salary should contain integers and Name should only contain characters. I need validations for my TextBox, it should show errors as I enter wrong characters or integers. Also can this be done only in Xaml without codebehind? Please help me with the required code
This is Xaml code:
<TextBox Name="tb1" HorizontalAlignment="Left" Height="20" Margin="60,10,0,0" TextWrapping="NoWrap" Text="{Binding SelectedItem.Id,ElementName=dgsample}" VerticalAlignment="Top" Width="100" />
<TextBox Name="tb2" HorizontalAlignment="Left" Height="20" Margin="60,60,0,0" TextWrapping="NoWrap" Text="{Binding SelectedItem.Name, ElementName=dgsample}" VerticalAlignment="Top" Width="100"/>
<TextBox Name="tb3" HorizontalAlignment="Left" Height="20" Margin="60,110,0,0" TextWrapping="NoWrap" Text="{Binding SelectedItem.Salary, ElementName=dgsample}" VerticalAlignment="Top" Width="100"/>
There a 3 ways to implement validation:
Validation Rule
Implementation of INotifyDataErrorInfo
Implementation of IDataErrorInfo
Validation rule example:
public class NumericValidationRule : ValidationRule
{
public Type ValidationType { get; set; }
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string strValue = Convert.ToString(value);
if (string.IsNullOrEmpty(strValue))
return new ValidationResult(false, $"Value cannot be coverted to string.");
bool canConvert = false;
switch (ValidationType.Name)
{
case "Boolean":
bool boolVal = false;
canConvert = bool.TryParse(strValue, out boolVal);
return canConvert ? new ValidationResult(true, null) : new ValidationResult(false, $"Input should be type of boolean");
case "Int32":
int intVal = 0;
canConvert = int.TryParse(strValue, out intVal);
return canConvert ? new ValidationResult(true, null) : new ValidationResult(false, $"Input should be type of Int32");
case "Double":
double doubleVal = 0;
canConvert = double.TryParse(strValue, out doubleVal);
return canConvert ? new ValidationResult(true, null) : new ValidationResult(false, $"Input should be type of Double");
case "Int64":
long longVal = 0;
canConvert = long.TryParse(strValue, out longVal);
return canConvert ? new ValidationResult(true, null) : new ValidationResult(false, $"Input should be type of Int64");
default:
throw new InvalidCastException($"{ValidationType.Name} is not supported");
}
}
}
XAML:
Very important: don't forget to set ValidatesOnTargetUpdated="True" it won't work without this definition.
<TextBox x:Name="Int32Holder"
IsReadOnly="{Binding IsChecked,ElementName=CheckBoxEditModeController,Converter={converters:BooleanInvertConverter}}"
Style="{StaticResource ValidationAwareTextBoxStyle}"
VerticalAlignment="Center">
<!--Text="{Binding Converter={cnv:TypeConverter}, ConverterParameter='Int32', Path=ValueToEdit.Value, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"-->
<TextBox.Text>
<Binding Path="Name"
Mode="TwoWay"
UpdateSourceTrigger="PropertyChanged"
Converter="{cnv:TypeConverter}"
ConverterParameter="Int32"
ValidatesOnNotifyDataErrors="True"
ValidatesOnDataErrors="True"
NotifyOnValidationError="True">
<Binding.ValidationRules>
<validationRules:NumericValidationRule ValidationType="{x:Type system:Int32}"
ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<!--NumericValidationRule-->
</TextBox>
INotifyDataErrorInfo example:
public abstract class ViewModelBase : INotifyPropertyChanged, INotifyDataErrorInfo
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
ValidateAsync();
}
#endregion
public virtual void OnLoaded()
{
}
#region INotifyDataErrorInfo
private ConcurrentDictionary<string, List<string>> _errors = new ConcurrentDictionary<string, List<string>>();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public void OnErrorsChanged(string propertyName)
{
var handler = ErrorsChanged;
if (handler != null)
handler(this, new DataErrorsChangedEventArgs(propertyName));
}
public IEnumerable GetErrors(string propertyName)
{
List<string> errorsForName;
_errors.TryGetValue(propertyName, out errorsForName);
return errorsForName;
}
public bool HasErrors
{
get { return _errors.Any(kv => kv.Value != null && kv.Value.Count > 0); }
}
public Task ValidateAsync()
{
return Task.Run(() => Validate());
}
private object _lock = new object();
public void Validate()
{
lock (_lock)
{
var validationContext = new ValidationContext(this, null, null);
var validationResults = new List<ValidationResult>();
Validator.TryValidateObject(this, validationContext, validationResults, true);
foreach (var kv in _errors.ToList())
{
if (validationResults.All(r => r.MemberNames.All(m => m != kv.Key)))
{
List<string> outLi;
_errors.TryRemove(kv.Key, out outLi);
OnErrorsChanged(kv.Key);
}
}
var q = from r in validationResults
from m in r.MemberNames
group r by m into g
select g;
foreach (var prop in q)
{
var messages = prop.Select(r => r.ErrorMessage).ToList();
if (_errors.ContainsKey(prop.Key))
{
List<string> outLi;
_errors.TryRemove(prop.Key, out outLi);
}
_errors.TryAdd(prop.Key, messages);
OnErrorsChanged(prop.Key);
}
}
}
#endregion
}
View Model Implementation:
public class MainFeedViewModel : BaseViewModel//, IDataErrorInfo
{
private ObservableCollection<FeedItemViewModel> _feedItems;
[XmlIgnore]
public ObservableCollection<FeedItemViewModel> FeedItems
{
get
{
return _feedItems;
}
set
{
_feedItems = value;
OnPropertyChanged("FeedItems");
}
}
[XmlIgnore]
public ObservableCollection<FeedItemViewModel> FilteredFeedItems
{
get
{
if (SearchText == null) return _feedItems;
return new ObservableCollection<FeedItemViewModel>(_feedItems.Where(x => x.Title.ToUpper().Contains(SearchText.ToUpper())));
}
}
private string _title;
[Required]
[StringLength(20)]
//[CustomNameValidationRegularExpression(5, 20)]
[CustomNameValidationAttribute(3, 20)]
public string Title
{
get { return _title; }
set
{
_title = value;
OnPropertyChanged("Title");
}
}
private string _url;
[Required]
[StringLength(200)]
[Url]
//[CustomValidation(typeof(MainFeedViewModel), "UrlValidation")]
/// <summary>
/// Validation of URL should be with custom method like the one that implemented below, or with
/// </summary>
public string Url
{
get { return _url; }
set
{
_url = value;
OnPropertyChanged("Url");
}
}
public MainFeedViewModel(string url, string title)
{
Title = title;
Url = url;
}
/// <summary>
///
/// </summary>
public MainFeedViewModel()
{
}
public MainFeedViewModel(ObservableCollection<FeedItemViewModel> feeds)
{
_feedItems = feeds;
}
private string _searchText;
[XmlIgnore]
public string SearchText
{
get { return _searchText; }
set
{
_searchText = value;
OnPropertyChanged("SearchText");
OnPropertyChanged("FilteredFeedItems");
}
}
#region Data validation local
/// <summary>
/// Custom URL validation method
/// </summary>
/// <param name="obj"></param>
/// <param name="context"></param>
/// <returns></returns>
public static ValidationResult UrlValidation(object obj, ValidationContext context)
{
var vm = (MainFeedViewModel)context.ObjectInstance;
if (!Uri.IsWellFormedUriString(vm.Url, UriKind.Absolute))
{
return new ValidationResult("URL should be in valid format", new List<string> { "Url" });
}
return ValidationResult.Success;
}
#endregion
}
XAML:
<UserControl x:Class="RssReaderTool.Views.AddNewFeedDialogView"
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">
<FrameworkElement.Resources>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate x:Name="TextErrorTemplate">
<DockPanel LastChildFill="True">
<AdornedElementPlaceholder>
<Border BorderBrush="Red"
BorderThickness="2" />
</AdornedElementPlaceholder>
<TextBlock FontSize="20"
Foreground="Red">*?*</TextBlock>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError"
Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource=
{x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"></Setter>
</Trigger>
</Style.Triggers>
</Style>
<!--<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError"
Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>
</Style.Triggers>
</Style>-->
</FrameworkElement.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="5" />
<RowDefinition Height="Auto" />
<RowDefinition Height="5" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Feed Name"
ToolTip="Display" />
<TextBox Text="{Binding MainFeedViewModel.Title,UpdateSourceTrigger=PropertyChanged,ValidatesOnNotifyDataErrors=True,ValidatesOnDataErrors=True}"
Grid.Column="2" />
<TextBlock Text="Feed Url"
Grid.Row="2" />
<TextBox Text="{Binding MainFeedViewModel.Url,UpdateSourceTrigger=PropertyChanged,ValidatesOnNotifyDataErrors=True,ValidatesOnDataErrors=True}"
Grid.Column="2"
Grid.Row="2" />
</Grid>
</UserControl>
IDataErrorInfo:
View Model:
public class OperationViewModel : ViewModelBase, IDataErrorInfo
{
private const int ConstCodeMinValue = 1;
private readonly IEventAggregator _eventAggregator;
private OperationInfoDefinition _operation;
private readonly IEntityFilterer _contextFilterer;
private OperationDescriptionViewModel _description;
public long Code
{
get { return _operation.Code; }
set
{
if (SetProperty(value, _operation.Code, o => _operation.Code = o))
{
UpdateDescription();
}
}
}
public string Description
{
get { return _operation.Description; }
set
{
if (SetProperty(value, _operation.Description, o => _operation.Description = o))
{
UpdateDescription();
}
}
}
public string FriendlyName
{
get { return _operation.FriendlyName; }
set
{
if (SetProperty(value, _operation.FriendlyName, o => _operation.FriendlyName = o))
{
UpdateDescription();
}
}
}
public int Timeout
{
get { return _operation.Timeout; }
set
{
if (SetProperty(value, _operation.Timeout, o => _operation.Timeout = o))
{
UpdateDescription();
}
}
}
public string Category
{
get { return _operation.Category; }
set
{
if (SetProperty(value, _operation.Category, o => _operation.Category = o))
{
UpdateDescription();
}
}
}
public bool IsManual
{
get { return _operation.IsManual; }
set
{
if (SetProperty(value, _operation.IsManual, o => _operation.IsManual = o))
{
UpdateDescription();
}
}
}
void UpdateDescription()
{
//some code
}
#region Validation
#region IDataErrorInfo
public ValidationResult Validate()
{
return ValidationService.Instance.ValidateNumber(Code, ConstCodeMinValue, long.MaxValue);
}
public string this[string columnName]
{
get
{
var validation = ValidationService.Instance.ValidateNumber(Code, ConstCodeMinValue, long.MaxValue);
return validation.IsValid ? null : validation.ErrorContent.ToString();
}
}
public string Error
{
get
{
var result = Validate();
return result.IsValid ? null : result.ErrorContent.ToString();
}
}
#endregion
#endregion
}
XAML:
<controls:NewDefinitionControl x:Class="DiagnosticsDashboard.EntityData.Operations.Views.NewOperationView"
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"
xmlns:views="clr-namespace:DiagnosticsDashboard.EntityData.Operations.Views"
xmlns:controls="clr-namespace:DiagnosticsDashboard.Core.Controls;assembly=DiagnosticsDashboard.Core"
xmlns:c="clr-namespace:DiagnosticsDashboard.Core.Validation;assembly=DiagnosticsDashboard.Core"
mc:Ignorable="d">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="40" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Label Grid.Column="0"
Grid.Row="0"
Margin="5">Code:</Label>
<Label Grid.Column="0"
Grid.Row="1"
Margin="5">Description:</Label>
<Label Grid.Column="0"
Grid.Row="2"
Margin="5">Category:</Label>
<Label Grid.Column="0"
Grid.Row="3"
Margin="5">Friendly Name:</Label>
<Label Grid.Column="0"
Grid.Row="4"
Margin="5">Timeout:</Label>
<Label Grid.Column="0"
Grid.Row="5"
Margin="5">Is Manual:</Label>
<TextBox Grid.Column="1"
Text="{Binding Code,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"
Grid.Row="0"
Margin="5"/>
<TextBox Grid.Column="1"
Grid.Row="1"
Margin="5"
Text="{Binding Description}" />
<TextBox Grid.Column="1"
Grid.Row="2"
Margin="5"
Text="{Binding Category}" />
<TextBox Grid.Column="1"
Grid.Row="3"
Margin="5"
Text="{Binding FriendlyName}" />
<TextBox Grid.Column="1"
Grid.Row="4"
Margin="5"
Text="{Binding Timeout}" />
<CheckBox Grid.Column="1"
Grid.Row="5"
Margin="5"
IsChecked="{Binding IsManual}"
VerticalAlignment="Center" />
</Grid>
</controls:NewDefinitionControl>
You can additionally implement IDataErrorInfo as follows in the view model. If you implement IDataErrorInfo, you can do the validation in that instead of the setter of a particular property, then whenever there is a error, return an error message so that the text box which has the error gets a red box around it, indicating an error.
class ViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private string m_Name = "Type Here";
public ViewModel()
{
}
public string Name
{
get
{
return m_Name;
}
set
{
if (m_Name != value)
{
m_Name = value;
OnPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public string Error
{
get { return "...."; }
}
/// <summary>
/// Will be called for each and every property when ever its value is changed
/// </summary>
/// <param name="columnName">Name of the property whose value is changed</param>
/// <returns></returns>
public string this[string columnName]
{
get
{
return Validate(columnName);
}
}
private string Validate(string propertyName)
{
// Return error message if there is error on else return empty or null string
string validationMessage = string.Empty;
switch (propertyName)
{
case "Name": // property name
// TODO: Check validiation condition
validationMessage = "Error";
break;
}
return validationMessage;
}
}
And you have to set ValidatesOnDataErrors=True in the XAML in order to invoke the methods of IDataErrorInfo as follows:
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
To get it done only with XAML you need to add Validation Rules for individual properties. But i would recommend you to go with code behind approach.
In your code, define your specifications in properties setters and throw exceptions when ever it doesn't compliance to your specifications.
And use error template to display your errors to user in UI.
Your XAML will look like this
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="CustomTextBoxTextStyle" TargetType="TextBox">
<Setter Property="Foreground" Value="Green" />
<Setter Property="MaxLength" Value="40" />
<Setter Property="Width" Value="392" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Trigger.Setters>
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Background" Value="Red"/>
</Trigger.Setters>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TextBox Name="tb2" Height="30" Width="400"
Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=True}"
Style="{StaticResource CustomTextBoxTextStyle}"/>
</Grid>
Code Behind:
public partial class MainWindow : Window
{
private ExampleViewModel m_ViewModel;
public MainWindow()
{
InitializeComponent();
m_ViewModel = new ExampleViewModel();
DataContext = m_ViewModel;
}
}
public class ExampleViewModel : INotifyPropertyChanged
{
private string m_Name = "Type Here";
public ExampleViewModel()
{
}
public string Name
{
get
{
return m_Name;
}
set
{
if (String.IsNullOrEmpty(value))
{
throw new Exception("Name can not be empty.");
}
if (value.Length > 12)
{
throw new Exception("name can not be longer than 12 charectors");
}
if (m_Name != value)
{
m_Name = value;
OnPropertyChanged("Name");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I have implemented this validation. But you would be used code behind. It is too much easy and simplest way.
XAML:
For name Validtion only enter character from A-Z and a-z.
<TextBox x:Name="first_name_texbox" PreviewTextInput="first_name_texbox_PreviewTextInput" > </TextBox>
Code Behind.
private void first_name_texbox_PreviewTextInput ( object sender, TextCompositionEventArgs e )
{
Regex regex = new Regex ( "[^a-zA-Z]+" );
if ( regex.IsMatch ( first_name_texbox.Text ) )
{
MessageBox.Show("Invalid Input !");
}
}
For Salary and ID validation, replace regex constructor passed value with [0-9]+. It means you can only enter number from 1 to infinite.
You can also define length with [0-9]{1,4}. It means you can only enter less then or equal to 4 digit number. This baracket means {at least,How many number}. By doing this you can define range of numbers in textbox.
May it help to others.
XAML:
Code Behind.
private void salary_texbox_PreviewTextInput ( object sender, TextCompositionEventArgs e )
{
Regex regex = new Regex ( "[^0-9]+" );
if ( regex.IsMatch ( salary_texbox.Text ) )
{
MessageBox.Show("Invalid Input !");
}
}
When it comes to Muhammad Mehdi's answer, it is better to do:
private void salary_texbox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
Regex regex = new Regex ( "[^0-9]+" );
if(regex.IsMatch(e.Text))
{
MessageBox.Show("Error");
}
}
Because when comparing with the TextCompositionEventArgs it gets also the last character, while with the textbox.Text it does not. With textbox, the error will show after next inserted character.
When I needed to do this, I followed Microsoft's example using Binding.ValidationRules and it worked first time.
See their article, How to: Implement Binding Validation:
https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/how-to-implement-binding-validation?view=netframeworkdesktop-4.8
When it comes to DiSaSteR's answer, I noticed a different behavior. textBox.Text shows the text in the TextBox as it was before the user entered a new character, while e.Text shows the single character the user just entered. One challenge is that this character might not get appended to the end, but it will be inserted at the carret position:
private void salary_texbox_PreviewTextInput(object sender, TextCompositionEventArgs e){
Regex regex = new Regex ( "[^0-9]+" );
string text;
if (textBox.CaretIndex==textBox.Text.Length) {
text = textBox.Text + e.Text;
} else {
text = textBox.Text.Substring(0, textBox.CaretIndex) + e.Text + textBox.Text.Substring(textBox.CaretIndex);
}
if(regex.IsMatch(text)){
MessageBox.Show("Error");
}
}

Categories

Resources