How can I make DataGridComboBoxColumn display the user phone numbers (right now it displays a blank combobox)?
EDIT: Code below has been updated and now works.
<Window x:Class="DataGridTest.MainWindow"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:DataGridTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid
x:Name="myDataGrid"
Grid.Row="1"
AutoGenerateColumns="False"
CanUserAddRows="True"
ItemsSource="{Binding PersonList}"
>
<DataGrid.Columns>
<DataGridTextColumn
Header="LastName"
Binding="{Binding LastName}"/>
<DataGridTextColumn
Header="FirstName"
Binding="{Binding FirstName}" />
<DataGridComboBoxColumn
Header="Phone Number"
>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding PhoneNumbers}" />
<Setter Property="SelectedValue" Value="{Binding SelectedPhoneNumber}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding PhoneNumbers}" />
<Setter Property="SelectedValue" Value="{Binding SelectedPhoneNumber}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
// code behind
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace DataGridTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<Person> PersonList { get; set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
PersonList = new ObservableCollection<Person>();
Person jjim = new Person() { FirstName = "jimmy", LastName = "jim" };
jjim.PhoneNumbers.Add("123-4567");
jjim.PhoneNumbers.Add("234-5678");
Person mmark = new Person() { FirstName = "mike", LastName = "mark" };
mmark.PhoneNumbers.Add("345-6789");
mmark.PhoneNumbers.Add("456-7890");
PersonList.Add(jjim);
PersonList.Add(mmark);
}
}
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> PhoneNumbers { get; set; }
private string selectedPhoneNumber;
public string SelectedPhoneNumber
{
get
{
return this.selectedPhoneNumber;
}
set
{
this.selectedPhoneNumber = value;
RaisePropertyChanged("SelectedPhoneNumber");
}
}
public Person()
{
PhoneNumbers = new ObservableCollection<string>();
}
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string firstName;
private string lastName;
public string FirstName
{
get
{
return this.firstName;
}
set
{
this.firstName = value;
RaisePropertyChanged("FirstName");
}
}
public string LastName
{
get
{
return this.lastName;
}
set
{
this.lastName = value;
RaisePropertyChanged("LastName");
}
}
}
}
A DataGridColumn is not a visual element and doesn't have a DataContext and therefore your binding won't work. You could easily fix this by specifying an ElementStyle and and EditingElementStyle for the DataGridComboBoxColumn:
<DataGridComboBoxColumn Header="Phone Number">
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding PhoneNumbers}" />
</Style>
</DataGridComboBoxColumn.ElementStyle>
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="ItemsSource" Value="{Binding PhoneNumbers}" />
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>
This works because the actual ComboBox element that eventually gets added to the visual tree by the DataGridComboBoxColumn and that the styles are getting applied to always has a DataContext.
Please refer to the following link for more examples of how to data bind to a ComboBox in WPF: https://code.msdn.microsoft.com/windowsdesktop/Best-ComboBox-Tutorial-5cc27f82#content
Have a look at my anser to this question: DataGridComboBoxColumn is empty
Seems like the same problem. The DataGridComboBoxColumn is a bit buggy.
Related
I tried this sample How to develop treeview with checkboxes in wpf? to create a TreeView with checkbox but in this example you cannot have a family in another family.
So my question is how to create a full treeview with check box not only limited in one level?
So this is what I did
IParent.cs
interface IParent<T>
{
IEnumerable<T> GetChildren();
}
DataModel.cs
using System;
using System.Collections.Generic;
using System.Windows;
public class Family : DependencyObject, IParent<object>
{
public string Name { get; set; }
public List<Person> Members { get; set; }
IEnumerable<object> IParent<object>.GetChildren()
{
return Members;
}
}
public class Person : DependencyObject
{
public string Name { get; set; }
}
ItemHelper.cs
using System.Linq;
using System.Windows;
public class ItemHelper : DependencyObject
{
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), typeof(ItemHelper),
new PropertyMetadata(false, new PropertyChangedCallback(OnIsCheckedPropertyChanged)));
private static void OnIsCheckedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
IParent<object> sect = d as IParent<object>;
DependencyObject depObj = d as DependencyObject;
if (sect != null)
{
if (((bool?)e.NewValue).HasValue)
{
foreach (DependencyObject p in sect.GetChildren())
{
SetIsChecked(p, (bool?)e.NewValue);
}
}
}
if (depObj != null)
{
var parentObject = depObj.GetValue(ParentProperty) as IParent<object>;
var parentDO = depObj.GetValue(ParentProperty) as DependencyObject;
int ch = parentObject?.GetChildren()?.Where(
x => GetIsChecked(x as DependencyObject) == true).Count() ?? 0;
int un = parentObject?.GetChildren()?.Where(
x => GetIsChecked(x as DependencyObject) == false).Count() ?? 0;
if (un > 0 && ch > 0)
{
SetIsChecked(parentDO, null);
return;
}
if (ch > 0)
{
SetIsChecked(parentDO, true);
return;
}
SetIsChecked(parentDO, false);
}
}
public static void SetIsChecked(DependencyObject element, bool? IsChecked)
{
element?.SetValue(IsCheckedProperty, IsChecked);
}
public static bool? GetIsChecked(DependencyObject element)
{
return (bool?)element?.GetValue(IsCheckedProperty);
}
public static readonly DependencyProperty ParentProperty =
DependencyProperty.RegisterAttached("Parent", typeof(object), typeof(ItemHelper));
public static void SetParent(DependencyObject element, object Parent)
{
element?.SetValue(ParentProperty, Parent);
}
public static object GetParent(DependencyObject element)
{
return element?.GetValue(ParentProperty);
}
}
MainWindow.xaml
<Window x:Class="WpfApplication102.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication102"
Title="MainWindow" Height="220" Width="250">
<StackPanel>
<TreeView x:Name="treeView" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Families}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:Family}" ItemsSource="{Binding Members}" >
<CheckBox Content="{Binding Name}" IsChecked="{Binding Path=(local:ItemHelper.IsChecked), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(local:ItemHelper.IsChecked)}" Value="False" >
<Setter Property="Foreground" Value="LightGray"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:Person}" >
<CheckBox Content="{Binding Name}" IsChecked="{Binding Path=(local:ItemHelper.IsChecked), Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=(local:ItemHelper.IsChecked)}" Value="False" >
<Setter Property="Foreground" Value="LightGray"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<Button Content="?" Click="Button_PrintCrew_Click" />
<TextBlock x:Name="textBoxCrew"/>
</StackPanel>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication102
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<Family> Families { get; set; }
public MainWindow()
{
InitializeComponent();
this.Families = new ObservableCollection<Family>();
this.Families.Add(new Family() { Name = "Simpsons", Members = new List<Person>() { new Person() { Name = "Homer" }, new Person() { Name = "Bart" } } });
this.Families.Add(new Family() { Name = "Griffin", Members = new List<Person>() { new Person() { Name = "Peter" }, new Person() { Name = "Stewie" } } });
this.Families.Add(new Family() { Name = "Fry", Members = new List<Person>() { new Person() { Name = "Philip J." } } });
foreach (Family family in this.Families)
foreach (Person person in family.Members)
person.SetValue(ItemHelper.ParentProperty, family);
}
private void Button_PrintCrew_Click(object sender, RoutedEventArgs e)
{
string crew = "";
foreach (Family family in this.Families)
foreach (Person person in family.Members)
if (ItemHelper.GetIsChecked(person) == true)
crew += person.Name + ", ";
crew = crew.TrimEnd(new char[] { ',', ' ' });
this.textBoxCrew.Text = "Your crew: " + crew;
}
}
}
Like many other aspects of WPF development, this becomes much easier if you follow the MVVM pattern.
Create a common ViewModel base class to represent each node of the tree, with a collection of the same type to represent the node's children. This is where you would include the logic to handle ticking / unticking the parent / child items as appropriate. The TreeView's ItemSource is then bound to a simple collection of items to form the root level. The U.I. layout of the View is defined by a HierachicalDataTemplate for each distinct node type.
I covered this in a recent blog post, with sample code here.
I needed to invert Columns/Rows on my DataGrid (see WPF horizontal DataGrid and RotatedDataGrid)
Once I inverted it, I am having some weird effects on the images displayed inside my datagrid.
When I scroll down, the column 1 will crop the image by the left and a little on the right. The more I go down, the more it crops the left until there is nothing more.
How can I solve that ?
Here a full simple example if you want to test it (you just need to copy/paste it in a new project and scroll down to see the problem)
MainWindows.xaml
<Window x:Class="RotatedDataGrid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="150" Width="1000">
<Grid>
<DataGrid x:Name="MyRotatedDataGrid" HorizontalContentAlignment="Center"
ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Visible"
AutoGenerateColumns="True"
ItemsSource="{Binding Customers}">
<DataGrid.Resources>
<Style x:Key="DataGridBase" TargetType="Control">
<Setter Property="LayoutTransform">
<Setter.Value>
<TransformGroup>
<RotateTransform Angle="-90" />
<ScaleTransform ScaleX="1" ScaleY="-1" />
</TransformGroup>
</Setter.Value>
</Setter>
<Setter Property="TextOptions.TextFormattingMode" Value="Display" />
</Style >
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridBase}"/>
<Style TargetType="DataGridColumnHeader" BasedOn="{StaticResource DataGridBase}"/>
<Style TargetType="DataGridRowHeader" BasedOn="{StaticResource DataGridBase}"/>
</DataGrid.Resources>
<DataGrid.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="90" />
<MatrixTransform Matrix="-1, 0, 0, 1, 0, 0" />
</TransformGroup>
</DataGrid.LayoutTransform>
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" Padding="3"/>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text="{Binding Path=ItemCount}" Margin="8,0,4,0"/>
<TextBlock Text="Items"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace RotatedDataGrid
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ICollectionView Customers { get; private set; }
public ICollectionView GroupedCustomers { get; private set; }
public MainWindow()
{
InitializeComponent();
var _customers = new List<Customer>
{
new Customer
{
FirstName = "Christian",
LastName = "Moser",
Gender = Gender.Male,
WebSite = new Uri("http://www.wpftutorial.net"),
ReceiveNewsletter = true,
Image = "Images/christian.jpg"
},
new Customer
{
FirstName = "Peter",
LastName = "Meyer",
Gender = Gender.Male,
WebSite = new Uri("http://www.petermeyer.com"),
Image = "Images/peter.jpg"
},
new Customer
{
FirstName = "Lisa",
LastName = "Simpson",
Gender = Gender.Female,
WebSite = new Uri("http://www.thesimpsons.com"),
Image = "Images/lisa.jpg"
},
new Customer
{
FirstName = "Betty",
LastName = "Bossy",
Gender = Gender.Female,
WebSite = new Uri("http://www.bettybossy.ch"),
Image = "Images/betty.jpg"
}
};
Customers = CollectionViewSource.GetDefaultView(_customers);
GroupedCustomers = new ListCollectionView(_customers);
GroupedCustomers.GroupDescriptions.Add(new PropertyGroupDescription("Gender"));
MyRotatedDataGrid.DataContext = this;
}
}
public enum Gender
{
Male,
Female
}
public class Customer : INotifyPropertyChanged
{
private string _firstName;
private string _lastName;
private Gender _gender;
private Uri _webSite;
private bool _newsletter;
private string _image;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
NotifyPropertyChanged("FirstName");
}
}
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
NotifyPropertyChanged("LastName");
}
}
public Gender Gender
{
get { return _gender; }
set
{
_gender = value;
NotifyPropertyChanged("Gender");
}
}
public Uri WebSite
{
get { return _webSite; }
set
{
_webSite = value;
NotifyPropertyChanged("WebSite");
}
}
public bool ReceiveNewsletter
{
get { return _newsletter; }
set
{
_newsletter = value;
NotifyPropertyChanged("Newsletter");
}
}
public string Image
{
get { return _image; }
set
{
_image = value;
NotifyPropertyChanged("Image");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Private Helpers
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
Ok I found how to solve it.
The transformation applied into the DataGridCell was creating this scrollviewer problem. To solve that, I removed the layout transform on the DataGridCell (by removing the BaseOn code) and I applied the transformation into the DataGridCell Template.
WRONG
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridBase}"/>
RIGHT
<Style TargetType="DataGridCell">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid>
<Grid.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="90" />
<MatrixTransform Matrix="-1, 0, 0, 1, 0, 0" />
</TransformGroup>
</Grid.LayoutTransform>
<ContentPresenter/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I try to dynamically generate context menu with custom item template. But when user run some long time command, context menu freezes on screen. How to avoid context menu freezing?
Here a sample:
MainWindow.xaml:
<Window x:Class="MenuHangUpTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="525" Height="350">
<Grid Width="500" Height="300">
<TextBlock Text="Test test">
<TextBlock.ContextMenu>
<ContextMenu ItemsSource="{Binding ContextCommands}">
<ContextMenu.ItemContainerStyle>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Header" Value="{Binding Caption}" />
<Setter Property="Command" Value="{Binding Command}" />
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</Grid>
</Window>
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows;
using System.Windows.Input;
namespace MenuHangUpTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
public IEnumerable<TestCommandItem> ContextCommands
{
get
{
yield return new TestCommandItem();
}
}
}
public class TestCommandItem
{
public ICommand Command
{
get
{
return new TestCommand();
}
}
public string Caption
{
get
{
return "Test";
}
}
}
public class TestCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
// context menu freezes on screen during running this command
Thread.Sleep(5000);
MessageBox.Show("Done!");
}
}
}
In my WPF application, I want to add a context menu and its handler in ViewModel to the leaf nodes of my TreeView control. Here is how I have added the TreeView Control.
<TreeView Name="treePads" ItemsSource="{Binding pads}" Width="190">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type self:Pad}" ItemsSource="{Binding Members}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text=" [" Foreground="Blue" />
<TextBlock Text="{Binding Members.Count}" Foreground="Blue" />
<TextBlock Text="]" Foreground="Blue" />
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type self:PadInfo}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="["></TextBlock>
<TextBlock Text="{Binding SlotID}" />
<TextBlock Text="] ["></TextBlock>
<TextBlock Text="{Binding WellID}" />
<TextBlock Text="]"></TextBlock>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
Here is how the output of this code looks like:
I want to add a ContextMenu having two options: Rename, Delete and add there event handlers to the ViewModel. How can I do that?
This is good article on TreeView
http://www.codeproject.com/Articles/26288/Simplifying-the-WPF-TreeView-by-Using-the-ViewMode
Below I have shared a sample app which has a Rename Context Menu. The sample should help you to fix your problem.
XAML:
<TreeView Name="treeView">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!--<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />-->
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="Rename" Command="{Binding RenameCommand}"/>
</ContextMenu>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubElements}">
<StackPanel Orientation="Horizontal">
<!--<Image Margin="2" Source="{Binding ImageLocation}" Height="30" Width="30"/>-->
<TextBlock Margin="2" Text="{Binding HeaderText}" ></TextBlock>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Code behind
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Diagnostics;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<TreeViewElement> elements = new List<TreeViewElement>();
TreeViewElement mainElement = new TreeViewElement() { ImageLocation = "Images/1.png", HeaderText = "MainElement1" };
mainElement.SubElements = new List<TreeViewElement>();
mainElement.SubElements.Add(new TreeViewElement() { ImageLocation = "Images/2.png", HeaderText = "SubElement1" });
mainElement.SubElements.Add(new TreeViewElement() { ImageLocation = "Images/2.png", HeaderText = "SubElement2", BackgroundColor = "Blue" });
elements.Add(mainElement);
TreeViewElement mainElement2 = new TreeViewElement() { HeaderText = "MainElement2" };
elements.Add(mainElement2);
this.treeView.ItemsSource = elements;
}
}
public class TreeViewElement
{
public string ImageLocation { get; set; }
public string HeaderText { get; set; }
public string BackgroundColor { get; set; }
public List<TreeViewElement> SubElements { get; set; }
private ICommand _RenameCommand;
public ICommand RenameCommand
{
get {
if (_RenameCommand == null)
{
_RenameCommand = new RelayCommand((o) =>
{
// Your logic should go here
MessageBox.Show("HeaderText is " +HeaderText);
});
}
return _RenameCommand; }
}
}
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
}
I have a WPF application which consists of a TabControl. I have made a simple version of it for the purpose of this question:
In the first tab I have combobox which fills a datagrid. If I selected a row in the datagrid it gets bound a couple of textboxes and the user may edit its contents.
My objects in the datagrid implements the IDataErrorInfo interface and my textboxes has ValidatesOnDataErrors=True set in the {binding}. So if I erase the contents of the Name textbox it gets invalid (after the textbox loses focus):
Now, if it is invalid I don't want the user to be able to select another row in the datagrid, or select another row in the combobox (which would repopulate the datagrid). Basically I want the user to correct the name before he/she continues. Although, I would prefer if the user could switch tab.
So I either need to disable the controls to the left if the bound object is invalid or I need to set focus to the invalid textbox if I click on the controls to the left. I havn't found any suitable events or bindings for this. All ideas are appreciated.
Here is my XAML:
<Window x:Class="WpfValidationTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="350">
<TabControl>
<TabItem Header="Tab 1">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<ComboBox>
<ComboBox.Items>
<ComboBoxItem Content="Friends"/>
<ComboBoxItem Content="Business"/>
</ComboBox.Items>
</ComboBox>
<DataGrid Name="dg" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="Address" Binding="{Binding Address}" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
<StackPanel Orientation="Vertical" Width="200" Margin="10,0,0,0">
<TextBlock Text="Edit" FontWeight="Bold"/>
<TextBlock Text="Name:"/>
<TextBox Text="{Binding Path=SelectedItem.Name, ElementName=dg, ValidatesOnDataErrors=True}" />
<TextBlock Text="Address:"/>
<TextBox Text="{Binding Path=SelectedItem.Address, ElementName=dg, ValidatesOnDataErrors=True}" />
</StackPanel>
</StackPanel>
</TabItem>
<TabItem Header="Tab 2">
<TextBlock Text="The user should be able to navigate to this tab even if there are validation errors" TextWrapping="Wrap" />
</TabItem>
</TabControl>
</Window>
And here is the code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace WpfValidationTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Person> persons = new List<Person>()
{
new Person(){Name="John Doe", Address="My street 203"},
new Person(){Name="Jane Doe", Address="Your street 43"}
};
dg.ItemsSource = persons;
}
}
public class Person : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
switch (columnName)
{
case "Name":
if (string.IsNullOrEmpty(Name))
return "Name must be entered";
break;
case "Address":
if (string.IsNullOrEmpty(Address))
return "Address must be entered";
break;
}
return null;
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
private string _address;
public string Address
{
get { return _address; }
set
{
_address = value;
NotifyPropertyChanged("Address");
}
}
private void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
You can use a trigger to disable the control
<Style x:Key="disableOnValidation"
BasedOn="{StaticResource {x:Type DataGrid}}"
TargetType="{x:Type DataGrid}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=nameTextBox, Path=Validation.HasError}" Value="True">
<Setter Propert="IsEnabled" Value="False" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=addressTextbox, Path=Validation.HasError}" Value="True">
<Setter Propert="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>