How do I disable some controls based on validation in WPF? - wpf

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>

Related

Silverlight 5 ListBox IsSelected style binding broken?

i see Silverlight 5 bought style binding. Tried to apply it in a ListBox control, for multiple selection. I have the following XAML ListBox (the code works in a WPF application).
<ListBox ItemsSource="{Binding Values}" SelectionMode="Multiple">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="ListBoxItem">
<TextBlock Text="{Binding DisplayValue}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When i run this i get a binding error, it seems that it tries to find the IsSelected property on the type of "Values" collection instead of each individual item from that collection. Has anyone else experience this?
Update
Added full code to reproduce, you need to scroll the listbox to see the error in the output log
public class ValueViewModel : INotifyPropertyChanged
{
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
private string _displayValue;
public string DisplayValue
{
get { return _displayValue; }
set
{
_displayValue = value;
OnPropertyChanged("DisplayValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MainPageViewModel : INotifyPropertyChanged
{
public ObservableCollection<ValueViewModel> _values;
public ObservableCollection<ValueViewModel> Values
{
get { return _values; }
set
{
_values = value;
OnPropertyChanged("Values");
}
}
public MainPageViewModel()
{
Values = new ObservableCollection<ValueViewModel>();
for (var i = 0; i < 50; i++)
Values.Add(new ValueViewModel() { DisplayValue = i.ToString(), IsSelected = (i % 5) == 0 });
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And the XAML:
<Grid x:Name="LayoutRoot" Background="White" >
<Grid.Resources>
<viewmodels:MainPageViewModel x:Key="vmMainPage"/>
</Grid.Resources>
<Grid x:Name="workGrid" DataContext="{Binding Source={StaticResource vmMainPage}}">
<ListBox ItemsSource="{Binding Values}" SelectionMode="Multiple" Height="100">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="5" Text="{Binding DisplayValue}"/>
<TextBlock Margin="5" Text="{Binding IsSelected}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
Update 2
It seems the problem around the error is that in a scrollable situation if you select items 1 and then scroll down and select item 49 (in the above example), the 1st selection is lost.
I cannot reproduce it. It works fine for me. Here is a full working example based on your code. One issue that I did notice though is that when a ListBoxItem is rendered it automatically sets the property on the data object to false, regardless of whether it was true to begin with. So if you load up a list and set some of it's items to be pre-selected, all the items will be unselected when the ListBoxItems are rendered. One way to prevent this is to use Dispatcher.BeginInvoke and set the selected items there. See my comments in the code below.
XAML:
<UserControl x:Class="SilverlightApplication12.MainPage"
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"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400">
<Grid x:Name="LayoutRoot"
Background="White">
<ListBox ItemsSource="{Binding Entities}"
SelectionMode="Multiple">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected"
Value="{Binding Path=IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="ListBoxItem">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Margin="10 0 0 0"
Text="IsSelected:" />
<TextBlock Margin="5 0 0 0"
Text="{Binding IsSelected}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
Code-behind + entity class:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SilverlightApplication12
{
public partial class MainPage : UserControl, INotifyPropertyChanged
{
private ObservableCollection<MyEntity> _Entities;
public ObservableCollection<MyEntity> Entities
{
get { return _Entities; }
set
{
_Entities = value;
OnPropertyChanged("Entities");
}
}
public MainPage()
{
InitializeComponent();
Entities = new ObservableCollection<MyEntity>();
Entities.Add(new MyEntity()
{
Name = "One",
IsSelected = false,
});
Entities.Add(new MyEntity()
{
Name = "Two",
IsSelected = true,
//Even though this is initially true it does not matter.
//When the ListBoxItem is rendered it sets the property to false.
});
Entities.Add(new MyEntity()
{
Name = "Three",
IsSelected = false,
});
LayoutRoot.DataContext = this;
//Enable the following line to set the 2nd item to selected when the page is loaded.
//Dispatcher.BeginInvoke(() => Entities[1].IsSelected = true);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class MyEntity : INotifyPropertyChanged
{
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
OnPropertyChanged("Name");
}
}
private bool _IsSelected;
public bool IsSelected
{
get
{
return _IsSelected;
}
set
{
_IsSelected = value;
OnPropertyChanged("IsSelected");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Everything except this <DataTemplate DataType="ListBoxItem"> looks fine to me.
If the Values collection is a collection of ListBoxItems, you don't need the IsSelected binding.
Otherwise, the DataType on your DataTemplate is wrong and should probably be left blank.
So i've managed to find a workarround that seems to do the job for my needs. It will set the already loaded values once the Loaded event gets triggered. And it wraps the MouseDown event to set the selection status. It's not a true databind but gets the job done, and still keeps the View clean of code.
<ListBox ItemsSource="{Binding Values}" SelectionMode="Multiple" Height="100">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Margin="2" Text="{Binding DisplayValue, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<ei:ChangePropertyAction
TargetObject="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
PropertyName="IsSelected"
Value="{Binding IsSelected}"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeftButtonDown">
<ei:ChangePropertyAction
TargetObject="{Binding}"
PropertyName="IsSelected"
Value="{Binding IsSelected, Converter={StaticResource invertBooleanConverter}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Make first row of DataGrid ReadOnly

I'm using a DataGrid to display user permissions in my WPF-application.
The first row of the DataGrid will always contain the owner of the project for which the permissions are displayed.
This owner is set when the project is created and can not be changed directly from the DataGrid.
My question then is.
How can I make the first row ReadOnly, and maybe give it a specific style so the background can be changed?
You will need a trigger for this, the only problem is that getting a row index on the wpf datagrid is pretty awful, so i tend to do something like this:
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsOwner}" Value="true">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
where there's a property value on the object i know will be unique to that particular row. So if there's a unique ID or something that the owner always has, you can bind to that.
The setter property is 'IsEnabled' as datagridrow doesn't contain a property for readonly, but this will stop a user modifying the row.
Here's my take on it. The previous sample will do you just fine, my sample is for demonstration of the approach for acquiring low level control over the cells look. In WPF DataGridRow is just a logical container, you can only use 'attached' properties with it, such as Enabled, FontSize, FontWeight etc., as they'll get propagated down to the cell level), but the actual control's look is defined at a cell level.
TextBlock's for readonly stuff generally look cleaner than disabled texboxes, also you might want to apply completely different style for readonly and editable modes of your cells, for which you'll have to do somewhat similar to what the code below does.
Code:
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;
namespace ReadOnlyRows
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += (o, e) =>
{
this.g.ItemsSource = new List<Person>(2)
{
new Person(){ Name="Dmitry", Role="Owner" },
new Person(){ Name="Jody", Role="BA" }
};
};
}
}
public class Person
{
public string Role
{
get;
set;
}
public string Name
{
get;
set;
}
}
public class PersonServices
{
// that shouldn't be in template selector, whould it?
public static bool CanEdit(Person person)
{
return person.Role != "Owner";
}
}
public class TemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Person person = item as Person;
if (person == null) return null;
string templateName = PersonServices.CanEdit(person) ? "EditableDataTemplate" : "ReadOnlyDataTemplate";
return (DataTemplate)((FrameworkElement)container).FindResource(templateName);
}
}
public class EditingTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Person person = item as Person;
if (person == null) return null;
string templateName = PersonServices.CanEdit(person) ? "EditableEditingDataTemplate" : "ReadOnlyEditingDataTemplate";
return (DataTemplate)((FrameworkElement)container).FindResource(templateName);
}
}
}
XAML:
<Window x:Class="ReadOnlyRows.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ReadOnlyRows"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid x:Name="g" AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Resources>
<DataTemplate x:Key="EditableEditingDataTemplate">
<TextBox Text="{Binding Name}" />
</DataTemplate>
<DataTemplate x:Key="ReadOnlyEditingDataTemplate">
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</DataTemplate>
<DataTemplate x:Key="EditableDataTemplate">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
<DataTemplate x:Key="ReadOnlyDataTemplate">
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</DataTemplate>
<local:TemplateSelector x:Key="TemplateSelector" />
<local:EditingTemplateSelector x:Key="EditingTemplateSelector" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name"
CellTemplateSelector="{StaticResource TemplateSelector}"
CellEditingTemplateSelector="{StaticResource EditingTemplateSelector}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>

How can I prevent the entire DataGrid row from being selected on the first click?

I have WPF Took Kit DataGrid with checkbox. User should be able to check or uncheck item upon single click. But I am having the following mouse-click-behavior issue.
On first-Mouse-Click, it selects entire row, and on subsequent clicks it selects or deselects check box.
Now how do I avoid first-mouse-click behavior, so that user does not have to click multiple times?
I would prefer DataGrid with checkbox without third party tool like WPF Tool Kit. I would appreciate if somebody can provide me URL to sample code without WPF tool kit.
TasklistView.xaml
<UserControl x:Class="MyProject.TasklistView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:toolkit="http://schemas.microsoft.com/wpf/2008/toolkit"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="742" Width="1024" >
<Grid>
<toolkit:DataGrid x:Name="dgMyTaskList"
SelectionMode="Single"
ItemsSource="{Binding Path=MyTaskList}"
AutoGenerateColumns="False"
SelectedItem="{Binding Path=SelectedTaskItem,
UpdateSourceTrigger=PropertyChanged}" >
<toolkit:DataGrid.Columns>
<toolkit:DataGridCheckBoxColumn
Header="Select"
Binding="{Binding Path=IsSelected}" />
<toolkit:DataGridTextColumn
Header="Task ID"
Width="100"
Binding="{Binding TaskID}"
IsReadOnly="True" />
<toolkit:DataGridTextColumn
Header="Task Description"
Width="*" Binding="{Binding TaskDescription}"
IsReadOnly="True" />
<toolkit:DataGridTextColumn
Header="Due Date" Width="*"
Binding="{Binding DueDate}"
IsReadOnly="True" />
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>
</Grid>
</UserControl>
TasklistViewModel.cs
using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Windows.Forms;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Controls;
using System.Collections.ObjectModel;
using System.Text;
using System.IO;
namespace MyProject
{
public class TasklistViewModel: ViewModelBase
{
ObservableCollection<TaskModel> _TaskList;
public TasklistViewModel()
{
_MyTaskList= dal.GetTaskList());
}
public ObservableCollection<TaskModel> MyTaskList
{
get { return _MyTaskList; }
set
{
if (_MyTaskList!= value)
{
this._MyTaskList= value;
this.OnPropertyChanged("MyTaskList");
}
}
}
private TaskModel _SelectedTaskItem;
public TaskModel SelectedTaskItem
{
get { return _SelectedTaskItem; }
set
{
if (value != null)
{
_SelectedTaskItem= value;
OnPropertyChanged("SelectedTaskItem");
if (null != _SelectedTaskItem)
{
ObservableCollection<TaskModel> oCol =
new ObservableCollection<TaskModel>();
foreach (TaskModel itm in MyTaskList)
{
if (itm.TaskID == _SelectedTaskItem.BundleID)
{
itm.IsSelected = _SelectedTaskItem.IsSelected;
}
oCol.Add(itm);
}
MyTaskList.Clear();
MyTaskList = oCol;
OnPropertyChanged("MyTaskList");
}
}
}
}
}
}
I solved the same problem by applying a cell style on the column containing the checkbox
MyWindow.xaml
<DataGridCheckBoxColumn Header="Select" Binding="{Binding Path=IsSelected}">
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell">
<EventSetter Event="MouseLeftButtonUp" Handler="ApprovedMouseUp"></EventSetter>
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
MyWindow.cs
private void ApprovedMouseUp(object sender, MouseButtonEventArgs e)
{
if(sender is DataGridCell)
{
var temp = (sender as DataGridCell).Content;
if(temp is CheckBox) (temp as CheckBox).IsChecked = !(temp as CheckBox).IsChecked;
}
}
Im not using the WPF toolkit but the DataGrid i .NET 4.0.
Never the less, Im quite sure it will work anyway

custom control in DataGridTemplateColumn

I'd like to add my custom control into a template column of data grid.
The custom control is very similar to a text box, but has an icon in it. The user can click the icon, and selects an item from a prompted window, then the selected item will be filled into the text box.
My problem is when the text box is filled, after I click the second column, the text will disappear. If I replace the custom control with a simple text box, the result is the same.
Here is the sample code:
//Employee.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SimpleGridTest
{
public class Employee
{
public string Department { get; set; }
public int ID { get; set; }
public string Name { get; set; }
}
}
Mainwindow.xaml
<Window x:Class="SimpleGridTest.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">
<Grid>
<DataGrid x:Name="grid" Grid.Row="1" Margin="5" AutoGenerateColumns="False"
RowHeight="25" RowHeaderWidth="10"
ItemsSource="{Binding}"
CanUserAddRows="True" CanUserSortColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Department" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Department}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="ID" Binding="{Binding Path=ID}"
Width="100"/>
<DataGridTextColumn Header="Name"
Binding="{Binding Path=Name}"
Width="200"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Collections.ObjectModel;
namespace SimpleGridTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ObservableCollection<Employee> _employees = new ObservableCollection<Employee>();
public ObservableCollection<Employee> Employees
{
get { return _employees; }
set { _employees = value; }
}
public MainWindow()
{
InitializeComponent();
grid.ItemsSource = Employees;
}
}
}
How can I fix this problem? Or I need to write a DataGrid***Column as DataGridTextColumn? Thanks in advance!
Best Regards,
Johnson
I guess you have to specify a CellEditingTemplate for editing and to display content you have to specify a normal celltemplate
<Controls:DataGridTemplateColumn Header="Department" Width="150">
<Controls:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Department}" />
</DataTemplate>
</Controls:DataGridTemplateColumn.CellTemplate>
<Controls:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Department}"/>
</DataTemplate>
</Controls:DataGridTemplateColumn.CellEditingTemplate>
</Controls:DataGridTemplateColumn>
Hope this helps..

Click event for DataGridCheckBoxColumn

I have a DataGrid in a WPF form with a DataGridCheckBoxColumn, but I did not find any click event, Checked and unchecked for it...
Are these events available for the DataGridCheckBoxColumn? If not please suggest some workaround I could use.
Quoted from William Han's answer here: http://social.msdn.microsoft.com/Forums/ar/wpf/thread/9e3cb8bc-a860-44e7-b4da-5c8b8d40126d
It simply adds an event to the column. It is a good simple solution.
Perhaps you can use EventSetter as example below:
Markup:
<Window x:Class="DataGridCheckBoxColumnTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DataGridCheckBoxColumnTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:People x:Key="People"/>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{StaticResource People}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Name}" Header="Name"/>
<DataGridCheckBoxColumn Binding="{Binding Path=LikeCar}" Header="LikeCar">
<DataGridCheckBoxColumn.CellStyle>
<Style>
<EventSetter Event="CheckBox.Checked" Handler="OnChecked"/>
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Code:
using System;
using System.Windows;
namespace DataGridCheckBoxColumnTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
void OnChecked(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
}
}
namespace DataGridCheckBoxColumnTest
{
public class Person
{
public Person(string name, bool likeCar)
{
Name = name;
LikeCar = likeCar;
}
public string Name { set; get; }
public bool LikeCar { set; get; }
}
}
using System.Collections.Generic;
namespace DataGridCheckBoxColumnTest
{
public class People : List<Person>
{
public People()
{
Add(new Person("Tom", false));
Add(new Person("Jen", false));
}
}
}
Expanding on the DataGridCell concept noted above, this is what we used to get it working.
...XAML...
<DataGrid Grid.ColumnSpan="2" Name="dgMissingNames" ItemsSource="{Binding Path=TheMissingChildren}" Style="{StaticResource NameListGrid}" SelectionChanged="DataGrid_SelectionChanged">
<DataGrid.Columns>
<DataGridTemplateColumn CellStyle="{StaticResource NameListCol}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=Checked, UpdateSourceTrigger=PropertyChanged}" Name="theCheckbox" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding Path=SKU}" Header="Album" />
<DataGridTextColumn Binding="{Binding Path=Name}" Header="Name" "/>
<DataGridTextColumn Binding="{Binding Path=Pronunciation}" Header="Pronunciation" />
</DataGrid.Columns>
</DataGrid>
TheMissingChildren is an ObservableCollection object that contains the list of data elements including a boolean field "Checked" that we use to populate the datagrid.
The SelectionChanged code here will set the checked boolean in the underlying TheMissingChildren object and fire off a refresh of the items list. That ensures that the box will get checked off & display the new state no matter where you click on the row. Clicking the checkbox or somewhere in the row will toggle the check on/off.
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DataGrid ThisGrid = (DataGrid)sender;
CheckedMusicFile ThisMusicfile = (CheckedMusicFile)ThisGrid.SelectedItem;
ThisMusicfile.Checked = !ThisMusicfile.Checked;
ThisGrid.Items.Refresh();
}
<wpf:DataGridCheckBoxColumn Header="Cool?" Width="40" Binding="{Binding IsCool}"/>
How about something like this.
partial class SomeAwesomeCollectionItems : INotifyPropertyChanged
{
public event PropertyChanged;
protected void OnPropertyChanged(string property)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(property);
}
private bool _IsSelected;
public bool IsSelected { get { return _IsSelected; } set { _IsSelected = Value; OnPropertyChanged("IsSelected"); } }
}
Then in XAML
<DataGrid ItemsSource="{Binding Path=SomeAwesomeCollection"} SelectionMode="Single">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}"
BasedOn="{StaticResource {x:Type DataGridRow}}">
<!--Note that you will probably need to base on other style if you have stylized your DataGridRow-->
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
</Style>
</DataGrid.Resources
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
<!--More Columns-->
</DataGrid.Columns>
</DataGrid>
One note with this approach, however, is you may run into issues with virtualization and checked items not clearing (not sure, haven't tested with SelectionMode="Single"). If that is the case, the simplest workaround I have found to work is to turn virtualization off, but perhaps there is a better way to get around that particular issue.
If you do not want to add the event to your style you can also do it this way.
<DataGridCheckBoxColumn x:Name="name" Header="name?" Binding="{Path=Name}"
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
<EventSetter Event="CheckBox.Checked" Handler="Checked"/>
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>eckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
Try this in xml
<DataGridCheckBoxColumn Header="" IsThreeState="False" Binding="{Binding isCheck, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}" />
And in c#
public partial class UploadWindow : Window
{
ObservableCollection<Items> pl = new ObservableCollection<Items>();
class Items
{
public bool isCheck { get; set; }
}
public UploadWindow(Dictionary<string, object> ipDictionary)
{
InitializeComponent();
GridView1.ItemsSource = pl;
}
}
In my case, I need to find all checked checkboxes
var allChecked = pl.Where(x => x.isCheck == true);

Resources