WPF Listview - Get row where property changed - wpf

I have a listview with objects that have int IDs and string names. The name column is a textbox that is editable by the user. If the user changes a name, I want to be able to search the list for other objects with that same ID and change those names as well.
My big question is, I want to use the LostFocus property of the textbox to get the entire row, or object, instead of just the textbox.
The XAML below is greatly simplified, but I think it gets the basic idea across:
<ListView x:Name="linkList"
<GridViewColumn Width="75">
<GridViewColumn.CellTemplate>
<TextBox Text="{Binding linkName}" LostFocus="TextBox_LostFocus"/>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="75">
<GridViewColumn.CellTemplate>
<TextBox Text="{Binding linkID}"/>
</GridViewColumn.CellTemplate>
</GridViewColumn>
So in code behind:
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
//sender is the textbox in this case. so how
//would I get the object in that particular row so I can get its ID#?
}
I need to identify that particular row since the user can either click on a different row or press "enter" to save the name change. So going by the currently selected row is no good. Any ideas?

Google for the GetVisualAncestor<T> extension and use that.
((TextBox)sender).GetVisualAncestor<ListViewItem>();

Pretty obvious now, but I just had to find the ancestor of the type of ListViewItem and I'm able to pull the info I need from that object.
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
ListViewItem changedRow = GetAncestorOfType<ListViewItem>(sender as TextBox);
//now I can get the info I need from the changedRow object!
}
public T GetAncestorOfType<T>(FrameworkElement child) where T : FrameworkElement
{
var parent = VisualTreeHelper.GetParent(child);
if (parent != null && !(parent is T))
return (T)GetAncestorOfType<T>((FrameworkElement)parent);
return (T)parent;
}

Related

How can I perform a custom action on the data of a DataGridRow when it is selected?

I've been trying to figure out how to get this custom behaviour into a datagrid with out having much look when searching online for solutions.
Given the following datagrid (some xaml removed for brevity):
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Width="auto">
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Selected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I have the checkbox successfully bound to the databound object for each row. (Note: I'm using a DataGridTemplateColumn rather than DataGridCheckBoxColumn so that you do not need to double-click to change the value).
What I would like to achieve is to have the ability to tick the checkbox / update the Selected property of the databound object when the user selects a row. Effectively making the entire row click set the checked property of the checkbox. Ideally, I'd like to do this without a code behind file if possible as I'm trying to keep my code behinds as clean as possible.
Another feature which I would like, if possible, would be that clicking on a row would toggle it's selected property so that if you click on another one, the previous one stays selected as well as the new one.
Any help is much appreciated.
For clarity. I understood
Another feature which I would like, if possible, would be that
clicking on a row would toggle it's selected property so that if you
click on another one, the previous one stays selected as well as the
new one.
in the way, that you want the CheckBox of the an item, respectively the Selected property on the items ViewModel, to stay selected, when the next DataGridRow is selected, but not the DataGridRow itself? Is that correct?
My suggestion is to extend the behavior of your DataGrid using *WPF behavior*s (This is a good introduction. This way you can keep your codebehind clear, but don't have to twist XAML to make it do what you want.
This is basically the idea of behaviors: Writing testable code, which is not coupled to your concrete view, but nonetheless allowing you to write complicated stuff in 'real' code and not in XAML. In my opinion your case is a typical task for behaviors.
Your behavior could look about as simple as this.
public class CustomSelectionBehavior : Behavior<DataGrid>
{
protected override void OnAttached()
{
// Set mode to single to be able to handle the cklicked item alone
AssociatedObject.SelectionMode = DataGridSelectionMode.Single;
AssociatedObject.SelectionChanged += AssociatedObject_SelectionChanged;
}
protected override void OnDetaching()
{
AssociatedObject.SelectionChanged -= AssociatedObject_SelectionChanged;
}
private void AssociatedObject_SelectionChanged(object sender, SelectionChangedEventArgs args)
{
// Get DataContext of selected row
var item = args.AddedItems.OfType<ItemViewModel>();
// Toggle Selected property
item.Selected = !item.Selected;
}
}
Attaching the behavior to your specific DataGrid, is done in XAML:
<DataGrid ...>
<i:Interaction.Behaviors>
<b:CustomSelectionBehavior />
</i:Interaction.Behaviors>
...
</DataGrid>
You need to reference
System.Windows.Interactivity.dll
which contains the Behavior<T> baseclass as well.

Getting a selected value which is typed in an editable combobox?

I have a combobox and it's editable. So the user can select an item, but if the item doesn't exist, he can type in what ever he wants. But my problem is , if i select an existing item, everything works, and the value is set :
<ComboBox Height="23" SelectedIndex="0" HorizontalAlignment="Left" Margin="104,73,0,0" Name="comboBox1" VerticalAlignment="Top" Width="159" IsEditable="True" SelectionChanged="comboBox1_SelectionChanged" />
private void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ToetsAlgemeneGegevensViewModel vm = (ToetsAlgemeneGegevensViewModel)this.DataContext;
if (comboBox1.SelectedValue != null && vm != null)
{
vm.Examination.Course = comboBox1.SelectedValue.ToString();
}
But, if I type in something, how can i set this value? Someone who knows how to do this?
A quick answer:
I think you should better use ComboBox.Text property. Make a string property in your view model and bind it in the Text property: Text="{Binding MyStringProperty}".
Do what you do in your comboBox1_SelectionChanged inside the setter of your string property. I think this will be enough.

Trapping events within list box item templates in WPF

I've got listbox that employs an item template. Within each item in the list, as defined in the template there is a button. When the user clicks the button I change a value in the data source that defines the sort order of the list. Changing the datasource is not a problem as this is working just fine within my application template.
However my next step is to reload the listbox with the new sorted data source. I've tried doing this from the tempalte but it apparently doesn't have access (or I can't figure out how to get access) to the parent elements so I can reset the .ItemSource property with a newly sorted data source.
Seems like this is possible but the solution is eluding me :(
You could use databinding to bind the Button's Tag to its ListBox ancestor. Example:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="myDataTemplate">
<Button Tag="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}"
Click="Button_Click">MyButton</Button>
</DataTemplate>
</Grid.Resources>
<ListBox ItemTemplate="{StaticResource myDataTemplate}" ItemsSource="..." />
</Grid>
And here's the codebehind:
private void Button_Click(object sender, RoutedEventArgs e)
{
ListBox myListBox = (ListBox)((Button)sender).Tag;
...do something with myListBox...
}
Alternatively, you can manually climb the Visual Tree upwards in your code (no Tag data binding needed):
private void Button_Click(object sender, RoutedEventArgs e)
{
DependencyObject search = (DependencyObject)sender;
while (!(search is ListBox)) {
search = VisualTreeHelper.GetParent(search);
}
ListBox myListBox = (ListBox)search;
...do something with myListBox...
}

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
}

Dynamically updating a WPF ListView after changes

HI
I have this code
private void Button_Click(object sender, RoutedEventArgs e)
{
ObservableCollection<ListProduct> myList = new ObservableCollection<ListProduct>();
int index = myList.IndexOf((from parcour in myList
where parcour.name == myProduct.name
select parcour).FirstOrDefault());
if(index != -1)
{
myList[index].number++;
}
}
public class ListProduct
{
public string name { get; set; }
public int number{ get; set; }
}
XAML:
<ListView Name="ListView12" ItemsSource="{Binding}" Height="201">
<ListView.View>
<GridView>
<GridViewColumn Width="100" Header="name"
DisplayMemberBinding="{Binding name}" />
<GridViewColumn Width="100" Header="number"
DisplayMemberBinding="{Binding nombre}" />
</GridView>
</ListView.View>
</ListView>
this snippet is to modify the number of occrnce of an element on myListView
when I click the button and made those change myListView do not change. Any Help please -- is something missing?
If your code is exactly as you show it, this is because you are creating a new ObservableCollection which is local to the Button_Click method, and letting it go out of scope. You need to either set the DataContext to the new collection, or (more idiomatic) modify the elements of the collection which is your existing DataContext.
If you adopt the first option, you'll need to fix the code, because the LINQ query is running over the new (empty) collection and will always return null.
If you're already doing the second option, or change you code to use the second option, you still have a problem because ListProduct is not raising property change notification. So when you modify the number property, WPF has no way of detecting this change and updating the binding.
To fix this, implement INotifyPropertyChanged on the ListProduct class, and update your property setters to raise the PropertyChanged event. (You will not be able to use C# automatic properties; you must implement the property getters and setters by hand.)

Resources