How to trigger cellTemplateSelector when Items changed - wpf

I have 2 templates for DataGrid's CellTemplate. When I change the items, it won't help me select the template for me, my DisplayModeTemplateSelector won't even be called!
What I'm wondering is if there is a way to trigger this CellTemplateSelector again when items changed? How to refresh CellTemplate in DataGrid or ListView When Content Changes
<DataGridTemplateColumn x:Name="colorRange"
Width="*"
Header="Color Range">
<DataGridTemplateColumn.CellTemplateSelector>
<local:DisplayModeTemplateSelector HeatMapTemplate="{StaticResource heatMapTemplate}" ThreshHoldTemplate="{StaticResource threshHoldTemplate}" />
</DataGridTemplateColumn.CellTemplateSelector>
</DataGridTemplateColumn>
I found this blog
http://dotdotnet.blogspot.com/2008/11/refresh-celltemplate-in-listview-when.html
I think this is similar with my problem, but I really can't understand him! Can anyone explain it?

The solution in the blog post will not work with the DataGrid control because the DataGridTemplateColumn class doesn't belong to the Visual Tree, and even when I tried to bind it to a static class, I didn't suceed because of strange exceptions after property changes.
Anyway there is two possible ways to solve this problem.
1) The easier way.
Using the ObservableCollection class.
var itemIndex = 0;
var currentItem = vm.Items[itemIndex];
//Change necessary properties
//..
vm.Items.Remove(currentItem);
vm.Items.Insert(itemIndex, currentItem);
2) The more complex way.
You can add to your item class the property which returns the object itself.
public ItemViewModel(/*...*/)
{
this.SelfProperty = this;
//...
}
public ItemViewModel SelfProperty { get; private set; }
public void Update()
{
this.SelfProperty = null;
this.OnPropertyChanged("SelfProperty");
this.SelfProperty = this;
this.OnPropertyChanged("SelfProperty");
}
After that you can use the ContentControl.ContentTemplateSelector instead of the CellTemplateSelector like this:
<DataGridTemplateColumn Header="Color Range">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl Content="{Binding SelfProperty}" ContentTemplateSelector="{StaticResource mySelector}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
And when you change the property, call the Update method somehow:
currentItem.SomeDataProperty = "some new value";
//Or you can add this method call to the OnPropertyChanged
//so that it calls authomatically
currentItem.Update();
The reason why I've set a null value to the SelfProperty in the Update method first, is that the Selector will not update a template until the Content property is completely changed. If I set the same object once again - nothing will happen, but if I set a null value to it first - changes will be handled.

The easy way is to hook the Combo Box's Selection Changed event, and reassign the template selector. This forces a refresh.
In XAML (assume the rest of the DataGrid/ComboBoxColumn:
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.Gates, UpdateSourceTrigger=PropertyChanged}"/>
<EventSetter Event="SelectionChanged" Handler="GateIDChanged" />
</Style>
That refers to this DataGridTemplateColumn:
<DataGridTemplateColumn x:Name="GateParamsColumn" Header="Gate Parameters" CellTemplateSelector="{StaticResource GateParamsTemplateSelector}"></DataGridTemplateColumn>
And in the code behind:
private void GateIDChanged(object sender, SelectionChangedEventArgs eventArgs)
{
var selector = GateParamsColumn.CellTemplateSelector;
GateParamsColumn.CellTemplateSelector = null;
GateParamsColumn.CellTemplateSelector = selector;
}

Related

Getting edited value of DataGridTemplateColumn in DataGrid bound to LINQ query results

I have a WPF app with a datagrid that I am binding to a custom LINQ to SQL query in my ViewModel that brings together columns from two different tables:
public ICollectionView SetsView { get; set; }
public void UpdateSetsView()
{
var sets = (from s in dbContext.Sets
join o in dbContext.SetParts on s.ID equals o.SetID into g1
select new
{
s.ID,
s.SetNumber,
s.SetTitle,
s.SetType,
s.SetNotes,
s.SetUrl,
s.HaveAllParts,
s.NumberOfSets,
s.IsBuilt,
s.DateAdded,
s.LastModified,
UniqueParts = g1.Count(),
TotalParts = g1.Sum(o => o.Quantity)
}
);
SetsView = CollectionViewSource.GetDefaultView(sets);
}
The SetsView collection is bound to my datagrid and since I need to be able to edit the value of SetNotes for any row and save it back to the Sets table in my database I added an event handler for CellEditEnding ( CellEditEnding="dgST_Sets_CellEditEnding") to the DataGrid definition and created this column:
<DataGridTemplateColumn Header="Set Notes"
SortMemberPath="SetNotes"
Width="*">
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
</DataGridTemplateColumn.HeaderStyle>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding SetNotes, Mode=OneWay}" Margin="5,0,5,0"
HorizontalAlignment="Stretch" VerticalAlignment="Center" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding SetNotes, Mode=OneWay}" Margin="5,0,5,0"
HorizontalAlignment="Stretch" VerticalAlignment="Center"
/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
The issue is that when I run the application and edit the Set Notes column in any row I cannot work out how to get the new value of the edited cell from the event args. I thought I could cast the event args EditingElement instance to a TextBox (see handler below) but when I run the app, edit a row and change a value of SetNotes the type of EditingElement is ContentPresenter not TextBox and for the life of me I can't work out how to get the changed value.
private void dgST_Sets_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
string newValue = ((TextBox)e.EditingElement).Text;
//Code here to update table
}
Please remember I am binding to a custom LINQ to SQL query so this is not a classic model binding issue. Also note, when binding the value of SetNotes in my template column I have no option but using Mode=OneWay as using any other option gives me runtime errors about accessing a read only property - could this somehow be the issue?
I've spent hours on this and googled endlessly with no joy - can anyone help me out please?
This should work:
private void dgST_Sets_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
ContentPresenter cp = e.EditingElement as ContentPresenter;
if (cp != null && VisualTreeHelper.GetChildrenCount(cp) > 0)
{
TextBox textBox = VisualTreeHelper.GetChild(cp, 0) as TextBox;
if (textBox != null)
{
string newValue = textBox.Text;
}
}
}

How to create a IList Dep property in DataGridTextColumn custom control

I want to pass a list object to my Custom control of datagrid DataGridTextColumn.
for this I used this code
public class DataGridListBoxColumn : DataGridTextColumn
{
public IList<Student> ListItems
{
get { return (IList<Student>)GetValue(_ListItems); }
set { SetValue(_ListItems, value); }
}
public static readonly DependencyProperty _ListItems = DependencyProperty.Register("ListItems", typeof(IList<Student>), typeof(DataGridListBoxColumn));
}
I XAML
<local:DataGridListBoxColumn Binding="{Binding M_Name,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" ListItems="{Binding Path= stud, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MainWindow}}}" Width="100"/>
OR
<local:DataGridListBoxColumn Binding="{Binding M_Name,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" ListItems="{Binding RelativeSource={RelativeSource AncestorType=DataGridTextColumn}, Path=stud}" Width="100"/>
Both are not working, Is there any method to pass list to my Custom control
Thanks
The problem with DataGrid columns is, that they are not present in the visual or logical tree, therefore you can't use RelativeSource. The possibility, which also has restrictions, is to set Source for your Binding with x:Reference. Restriction is, that you can't set as source UIElement, which contains element with binding. So you can't set your main window or datagrid, which contains the column as binding's source otherwise you'll get cyclical dependency.
Now put next to your DataGrid a hidden control as access to the data, or use some existing:
<TextBlock x:Name="studAccess" Visibility="Collapsed"/>
<DataGrid>
<local:DataGridListBoxColumn .../>
</DataGrid>
and bind you dependency property of columns via this control(implied is, that DataContext of studAccess has a stud property):
<local:DataGridListBoxColumn Binding="{Binding M_Name,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}" ListItems="{Binding DataContext.stud, Source={x:Reference studAccess}}" Width="100"/>

selected item from listbox XAML

I am trying to get the selected item from the ListBox using listbox_SelectionChanged() method, but it does not seem to work. Could you tell me what is the best way to get the selected item out of listbox. the code I tried is bellow.
your help much appreciated.
XAML
<ListBox
x:Name="lbSkills"
Grid.Row="1"
Margin="10,0,10,10" SelectionChanged="LbSkills_SelectionChanged">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderThickness="0,0,0,1" BorderBrush="Beige">
<Grid Width="auto" HorizontalAlignment="Stretch">
<TextBlock VerticalAlignment="Center" FontSize="26" Grid.Column="0" Foreground="Black" Text="{Binding SkillDescription}"/>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
XAML.cs - I have also tried commented code, but unable to get the selected item
private async void LbSkills_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//var addedItems = e.AddedItems;
//string selectedSkillString = "None";
//if (addedItems.Count > 0)
//{
// var selectedSkill = addedItems[0];
// selectedSkillString = selectedSkill.ToString();
//}
//lbSkills.SelectedItem.ToString();
MessageDialog msgBox = new MessageDialog(e.AddedItems.ToString());
await msgBox.ShowAsync();
}
First of all check what is the DataConntext or ItemsSource of you ListBox (it have to be an ObservableCollection to avoid the memory leaks).
Check if there is a Binding errors in the Output window.
Check if there is a correcct property to bind to.
Try the solution the next solution:
As I can understand you, the problem is that the added items of event argument doesn't contains the current selected item. But there is no any problem with your code. It returns the actual model (Skill) when I used it. But if you apply ToString() metod on it, you won't get the real model, the result will be just the full name of a class (<Full.Assembly.Path>.<Class_Name>). If you want to get the model instance you have to cast or safely cast the e.AddedItems content or you have to override the ToString() method in your model class. From another hand if you want to get the ListBoxItem itself for some reason try to use the next code:
var listBox = sender as ListBox;
var selected = e.AddedItems.Cast<object>().FirstOrDefault();
var container = listBox.ItemContainerGenerator.ContainerFromItem(selected);
regards

WPF Datagrid selecteditem = null in MVVM

I'm trying to work with a datagrid using the MVVM pattern. The problem is that whenever I change the VM property which is binded to SelectedItem to null, the View doesn't "deselect" the currently selected item. This is my binding in xaml:
<DataGrid Grid.Column="0" Grid.Row="0"
ItemsSource="{Binding Path=Users}"
AutoGenerateColumns="False"
CanUserAddRows="False"
IsReadOnly="True"
SelectedItem="{Binding Path=SelectedUser, Mode=TwoWay}">
The SelectedItem binding works from the view to the VM thus in the SelectedUser property I always have the selected object. The problem is that in the VM I'm doing some stuff which sometimes changes the SelectedUser property to null so I would expect the datagrid to deselect the row as well. Instead, it remains selected and if I try to click on the same row, the property doesn't update. If I click on any other row, the property changes as expected.
Is there a way to make the datagrid deselect if it's binded property is set to null? Also I'm looking for a MVVM solution as I don't want to write code behind. I can solve this by writing code behind so don't waste time offering such solutions :)
l.e.: this is my property in the VM:
public RPLUser SelectedUser
{
get
{
return selectedUser;
}
set
{
selectedUser = value;
OnPropertyChanged("SelectedUser");
}
}
Thanks in advance!
I recommend to check the Output Window in visual studio and see if any Binding is failing.
Are you sure when you select something, the selection updates into the SelectedUser property?
Did u put a breakpoint in setter of SelectedUser and see that it is hitting when you select something on the datagrid?
The reasons for this Binding to break could be many ...
SelectedUser is of different type than individual Users.
SelectedUser does not match by reference with any items in Users.
How and where are you setting null?
The following code in my case works perfectly fine...
<tk:DataGrid MaxHeight="200" AutoGenerateColumns="False"
ItemsSource="{Binding}"
SelectedItem="{Binding MySelItem,
ElementName=MyDGSampleWindow,
Mode=TwoWay}"
IsReadOnly="True">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="Key"
Binding="{Binding Key,
Mode=OneWay}"/>
<tk:DataGridTextColumn Header="Value"
Binding="{Binding Value,
Mode=OneWay}"/>
</tk:DataGrid.Columns>
</tk:DataGrid>
When I set MyDGSampleWindow.MySelItem as null, the datagrid propertly deselects. Perhaps you might need to give us more input on how are you actually setting the value as null.
Did you try setting IsSynchronizedWithCurrentItem="True" in the xaml properties for the DataGrid? AFAIK, this will allow you to unselect it by setting the SelectedUser to null.
I cannot test it at the moment, but you could also try to add this in the setter of your property:
set
{
selectedUser = value;
OnPropertyChanged("SelectedUser");
ICollectionView collectionView = CollectionViewSource.GetDefaultView(Users);
collectionView.MoveCurrentTo(selectedUser);
}
(For ICollectionView to do anything, you will need to have IsSynchronizedWithCurrentItem set)
Like I said, I cannot test this right now. Also, the setter of the property is probably not the best place to put it. Maybe create an event handler for the PropertyChangedevent locally and put that logic there.
Let me know if it helps, else I'll see if I can run a short test...
Yeah may need to add the XAML UpdateSourceTrigger to update the UI.
SelectedItem="{Binding SomeProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
The DataGrid will not Deselect it automatically as DataGridRow's IsSelected property should be set to False.
You can do that by Setting a style on DataGrid.. like
<Style x:Key="dataGridRowStyle"
BasedOn="{StaticResource {x:Type WPFToolkit:DataGridRow}}"
TargetType="{x:Type WPFToolkit:DataGridRow}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />
</Style>
The IsSelected property should be the of the object i.e in your case RPLUser should have a property Isselected
Then before you set the SelectedUser to null... just do SelectedUser.IsSelected=False
And dont forget to attach this style to the DataGridRowStyle in Datagrid
I am using WPFToolkit you can modify the style if you are targetting .NET 4.0

How to select all the text when the edit textbox in a DataGridTemplateColumn receives focus?

I'm trying to get a DataGridTemplateColumn to behave identically to a TextColumn
when the cell goes into edit mode (Press F2), the user can immediately start typing in the new value
by default, existing text content is selected - so that you can set new values easily
Got the first one done ; however selecting all the text isn't working. As mentioned by a number of posts, tried hooking into the GotFocus event and selecting all the text in code-behind. This worked for a standalone textbox ; however for a Textbox which is the edit control for a TemplateColumn, this doesn't work.
Any ideas?
Code Sample:
<Window.Resources>
<Style x:Key="HighlightTextBoxStyle" TargetType="{x:Type TextBox}">
<EventSetter Event="GotFocus" Handler="SelectAllText"/>
<EventSetter Event="GotMouseCapture" Handler="SelectAllText"/>
<Setter Property="Background" Value="AliceBlue"/>
</Style>
<DataTemplate x:Key="DefaultTitleTemplate">
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
<DataTemplate x:Key="EditTitleTemplate">
<TextBox x:Name="Fox"
FocusManager.FocusedElement="{Binding RelativeSource={RelativeSource Self}}"
Text="{Binding Path=Title, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource HighlightTextBoxStyle}">
</TextBox>
</DataTemplate>
</Window.Resources>
<DockPanel>
<TextBox DockPanel.Dock="Top" x:Name="Test" Text="{Binding Path=(FocusManager.FocusedElement).Name, ElementName=MyWindow}"
Style="{StaticResource HighlightTextBoxStyle}"/>
<toolkit:DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<toolkit:DataGrid.Columns>
<toolkit:DataGridTemplateColumn Header="Templated Title"
CellTemplate="{StaticResource DefaultTitleTemplate}"
CellEditingTemplate="{StaticResource EditTitleTemplate}" />
<toolkit:DataGridTextColumn Header="Title" Binding="{Binding Path=Title}" />
</toolkit:DataGrid.Columns>
</toolkit:DataGrid>
</DockPanel>
Missed updating the post with an answer...
The problem seems to be that for a custom data grid column (aka a DataGridTemplateColumn) the grid has no way of knowing the exact type of the editing control (which is specified via a DataTemplate and could be anything). For a DataGridTextColumn, the editing control type is known and hence the grid can find it and invoke a SelectAll() in it.
So to achieve the end-goal for a TemplateColumn, you need to provide an assist. I forgotten how I solved it the first time around.. but here is something that I searched-tweaked out today. Create a custom derivation of a TemplateColumn with an override of the PrepareCellForEdit method as shown below (Swap Textbox with your exact editing control).
public class MyCustomDataColumn : DataGridTemplateColumn
{
protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)
{
var contentPresenter = editingElement as ContentPresenter;
var editingControl = FindVisualChild<TextBox>(contentPresenter);
if (editingControl == null)
return null;
editingControl.SelectAll();
return null;
}
private static childItem FindVisualChild<childItem>(DependencyObject obj)
}
Here's an implementation for FindVisualChild.
XAML:
<WPFTestBed:MyCustomDataColumn Header="CustomColumn"
CellTemplate="{StaticResource DefaultTitleTemplate}"
CellEditingTemplate="{StaticResource EditTitleTemplate}"/>
</DataGrid.Columns>
Lot of code for an annoying inconsistency.
I know this is way late but I took a different approach and creatively extended the TextBox class. I don't really like using the boolean to check if the text is already defined but the problem is that the selection events all fire before the text is set from the binding so SelectAll() doesn't have anything to select! This class is probably only useful as a editing template in something like a DataGridTemplateColumn. Every solution I found for this issue is pretty much a hack so I don't feel too bad about this one ... :)
class AutoSelectTextBox : TextBox
{
private bool _autoSelectAll= true;
protected override void OnInitialized(EventArgs e)
{
// This will cause the cursor to enter the text box ready to
// type even when there is no content.
Focus();
base.OnInitialized(e);
}
protected override OnKeyDown(System.Windows.Input.KeyEventArgs e)
{
// This is here to handle the case of an empty text box. If
// omitted then the first character would be auto selected when
// the user starts typing.
_autoSelectAll = false;
base.OnKeyDown(e);
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
if (_autoSelectAll)
{
SelectAll();
Focus();
_autoSelectAll= false;
}
base.OnTextChanged(e);
}
}
Kinda VERY late...just putting this out here in case someone can use this
I had a similar need to DeSelect (or Select All) text in a DataGridTextColumn on editing
Just added the method to the PreparingCellForEdit event of the DataGrid
DataGrid.PreparingCellForEdit += DataGrid_PreparingCellForEdit;
Then assigned the (e.EditingElement as TextBox) and then set my options
private void DataGrid_PreparingCellForEdit(object sender, DataGridPreparingCellForEditEventArgs e)
{
var txtBox = e.EditingElement as TextBox;
txtBox.Select(txtBox.Text.Length, 0); //to DeSelect all and place cursor at end
txtBox.SelectAll(); // to selectall
}

Resources