WPF datagrid selected row clicked event ? - wpf

I want to execute some code when a a selected row of the WPF DataGrid is double clicked. I know that the datagrid has a MouseDoubleClicked event and that it also has a row selected event but I don't see any event for "selected row double clicked" ...
Do you think it's possible to capture this event somehow ?

you can add the event handler in the ItemContainerStyle (which is the style applied to a row) :
<DataGrid ... >
<DataGrid.ItemContainerStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="MouseDoubleClick" Handler="Row_DoubleClick"/>
</Style>
</DataGrid.ItemContainerStyle>
...
</DataGrid>
Then, in the handler, you can check if the row is selected
private void Row_DoubleClick(object sender, MouseButtonEventArgs e)
{
// execute some code
}

This question came up for me while looking for a solution and the answers didn't work, whether due to age or my own implementation. Either way, here is the solution that worked for me.
Add the MouseDoubleClick event to the DataGrid
<DataGrid x:Name="DatagridMovie"
Width="Auto"
CanUserAddRows="False"
CanUserDeleteRows="True"
IsReadOnly="true"
ItemsSource="{Binding}"
MouseDoubleClick="Row_MouseDoubleClick">
and in the method
private void Row_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
// Ensure row was clicked and not empty space
var row = ItemsControl.ContainerFromElement((DataGrid)sender,
e.OriginalSource as DependencyObject) as DataGridRow;
if ( row == null ) return;
… Stuff();
}
So far I haven't noticed any problems with it. It doesn't share the problem that others have that means double clicking a header or empty space with a row selected beforehand would still cause it to run.

With data binding and MVVM you would do one-click event (=selectedItem of row) like this:
<Datagrid ItemsSource="{Binding YourObservableCollectionProperty}"
SelectedItem="{Binding YourSelectedItemProperty}">
//more...
</Datagrid>
CodeBehind:
public partial class YourClass : Window
{
public YourClass()
{
InitializeComponent();
this.DataContext = new YourClassViewModel();
}
}
ViewModel:
public class YourClassViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private ObservableCollection<YourModelClass> _yourObservableCollectionProperty;
public ObservableCollection<YourModelClass> YourObservableCollectionProperty
{
get { return _yourObservableCollectionProperty; }
set
{
_yourObservableCollectionProperty = value;
OnPropertyChanged("YourObservableCollectionProperty");
}
}
private YourModelClass _yourSelectedItemProperty;
public YourModelClass YourSelectedItemProperty
{
get { return _yourSelectedItemProperty; }
set
{
_yourSelectedItemProperty = value;
OnPropertyChanged("YourSelectedItemProperty");
}
}
//Constructor
public YourClassViewModel()
{
/*Take your ModelClass instance and ObservableCollection instance here
and play around with them or move them into a method. Normally your
observablecollection is the itemssource of your datagrid and your selecteditem
is your modelclass.*/
}
}

You could try current cell changed event handler it works only with one click and not double click if thats what your looking for, since double click can be used to for initiating editing cell or entire row or for any other process:
private void datagrid_CurrentCellChanged(object sender, EventArgs e)
{
int selected_index = datagrid.SelectedIndex + 1;
// this is used for debugging and testing.
//MessageBox.Show("The index of the row for the clicked cell is " + selected_index);
}

The ItemContainerStyle do not have best solution, suggest use the RowStyle:
In your XAML:
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="MouseDoubleClick" Handler="DataGridRow_MouseDoubleClick"/>
</Style>
</DataGrid.RowStyle>
In your Code:
private void DataGridRow_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
//your logic here
}

Why don't you get the SelectedRow property while the DoubleClick event happens and do something with it? If the SelectedRow is null, it means no Row is selected so just return
private void Grid_DoubleClick(object sender, RoutedEventArgs e)
{
if(grid.SelectedRow == null)
return; // return if there's no row selected
// do something with the Selected row here
}

Use rowstyle and MouseDoubleClick work, like Darlan Dieterich said.
But when there are button or checkbox or other controls in cell, they will handle event but not prevent event passing to row, cause weird behavior. Use MouseDown maybe better in these case.
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="MouseDown" Handler="DataGridRow_MouseDown"/>
</Style>
</DataGrid.RowStyle>
private void DataGridRow_MouseDown(object sender, MouseButtonEventArgs e)
{
if(e.ClickCount != 2)
{
return;
}
// code here
e.Handled = true;
}

Related

WPF Drag and Drop multiple listviewitems from listview to a button

I have a listview which property SelectionMode is set to Extended.
Also I have a trash button. I am trying to perform delete operation on selected items on listview when user drags one or multiple selected items (using shift key if more than one item) and drop over delete button.
I have implemented it with a single selected item in listview and it works. Now I am trying to do the same with multiple selected items in listview without success.
Below code works when one single item is selected in listview and user drag and drop it from listview to delete button:
<ListView Margin="10" Name="lvUsers" ItemsSource="{Binding Path=Items}" SelectionMode="Extended">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<EventSetter Event="MouseMove" Handler="lstItems_MouseMove" />
</Style>
</ListView.ItemContainerStyle>
<!-- other stuff -->
</Listview>
<Button AllowDrop="True" Drop="btnDelete_Drop" Height="64" Width="64" Margin="10" Click="BtnDelete_Click" Content="Delete"/>
and the code-behind:
private void lstItem_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (e.Source != null)
{
DataModel selectedItem = (DataModel)lvUsers.SelectedItem;
DragDrop.DoDragDrop(lvUsers, selectedItem, DragDropEffects.Move);
}
}
}
private void btnDelete_Drop(object sender, DragEventArgs e)
{
Type myType = typeof(DataModel);
string modelns = myType.FullName;
DataModel selectedItem = e.Data.GetData(modelns) as DataModel;
MessageBox.Show(selectedItem.Name);
}
Each listviewitem on listview is of data type below:
public class DataModel
{
public string Name
{
get;
set;
}
public int Age
{
get;
set;
}
public string Mail
{
get;
set;
}
}
As an example, when user drops the dragged listviewitem over delete button I show a message box with the name of the person.
How can I do the same but instead of dragging and dropping one single item from listview, do the same for multiple selected listviewitems? Once dropped, within
btnDelete_Drop I want to iterate over all the listviews items dropped and do some stuff.
I have just implemented a solution that works. I have replaced methods lstItem_MouseMove and btnDelete_Drop in code-behind by these ones:
private void lstItem_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
if (e.Source != null)
{
List<DataModel> myList = new List<DataModel>();
foreach (DataModel Item in lvUsers.SelectedItems)
{
myList.Add(Item);
}
DataObject dataObject = new DataObject(myList);
DragDrop.DoDragDrop(lvUsers, dataObject, DragDropEffects.Move);
}
}
}
private void btnDelete_Drop(object sender, DragEventArgs e)
{
Type myType = typeof(List<DataModel>);
List<DataModel> selectedItems = e.Data.GetData(myType) as List<DataModel>;
string hello = "Hello ";
foreach (DataModel dm in selectedItems)
{
hello = hello + ", " + dm.Name;
}
MessageBox.Show(hello);
}
This is working but mayber there are any other better solution. Any suggestion will be welcome.

wpf ListView item expand at mouseover

I have a ListView with pretty long listelement at times. I would like to create an event, where if I drag the mouse over an element, the whole name appears in a tooltip-like small window with the whole text of the item. This way the user can read it even if it is too long for the ListView window width.
I am a bit stuck, because I find no MouseOver event for the ListView elements. I would probably have to go on with a custom Style for my ListView, but I don't have experience with Styles.
I would really appreciate a little help, to get me started!
Create an AttachedProperty for MouseMove and hook your list view with the property. The attached property can be used to any element in your application.
Attached Property
public class MouseBehaviour
{
public static readonly DependencyProperty MouseMoveCommandProperty =
DependencyProperty.RegisterAttached("MouseMoveCommand", typeof(ICommand), typeof(MouseBehaviour), new FrameworkPropertyMetadata(new PropertyChangedCallback(MouseMoveCommandChanged)));
private static void MouseMoveCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = (FrameworkElement)d;
element.MouseMove += new MouseEventHandler(element_MouseMove);
}
static void element_MouseMove(object sender, MouseEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
ICommand command = GetMouseMoveCommand(element);
command.Execute(e);
}
public static void SetMouseMoveCommand(UIElement element, ICommand value)
{
element.SetValue(MouseMoveCommandProperty, value);
}
public static ICommand GetMouseMoveCommand(UIElement element)
{
return (ICommand)element.GetValue(MouseMoveCommandProperty);
}
}
XAML
xmlns:mousebehav="clr-namespace:your namespace"
<ListView mousebehav:MouseBehaviour.MouseMoveCommand="{Binding MouseMoveCommand}">
VM
private RelayCommand _MouseMoveCommand;
public RelayCommand MouseMoveCommand
{
get
{
if (_MouseMoveCommand== null) return _MouseMoveCommand= new RelayCommand(param => Execute_MouseMoveCommand((MouseEventArgs)param));
return _MouseMoveCommand;
}
set { _MouseMoveCommand= value; }
}
private void Execute_MouseMoveCommand(MouseEventArgs param)
{
//your logic to expand or ??
}
Thanks for the answer. After a few hours of experimenting, I managed to solve it quite compact from the xaml:
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="ToolTip" Value="{Binding Name}" />
</Style>
</ListView.ItemContainerStyle>

Text changed event fired when control loaded, need to prevent on load?

In I have created a control that has a text box and a text changed event handler attached to it - this is in xaml.
The problem: when control is loaded the text changed event is fired, I do not want it to happen when the control is loaded only when I make actually make it change on the control by typing something.
What do you pros suggest I do? :)
All you have to do is check the textbox's IsLoaded property inside the event handler before handling it.
Attach Your EventHandler after the InitializeComponent Method in your constructor not in the Xaml.
i.e.
public MainWindow()
{
InitializeComponent();
textBox1.TextChanged+=new TextChangedEventHandler(textBox1_TextChanged);
}
I noticed that you are talking about an usercontrol, the only thing I can think of off the top of my head is to to create a property that can be used to inhibit the TextChanged Event until the Parent Form finishes loading. See if something like this works.
MainForm Xaml:
<my:UserControl1 setInhibit="True" HorizontalAlignment="Left" Margin="111,103,0,0" x:Name="userControl11" VerticalAlignment="Top" Height="55" Width="149" setText="Hello" />
MainForm CS
private void Window_Loaded(object sender, RoutedEventArgs e)
{
userControl11.setInhibit = false;
}
UserControl:
public UserControl1()
{
InitializeComponent();
textBox1.TextChanged += new TextChangedEventHandler(textBox1_TextChanged);
}
public string setText
{
get { return textBox1.Text; }
set { textBox1.Text = value; }
}
public bool setInhibit { get; set; }
void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
if (setInhibit) return;
// Do your work here
}
UserControl1.xaml:
<Grid>
<TextBox Text="{Binding MyText, UpdateSourceTrigger=PropertyChanged}" TextChanged="TextBox_TextChanged"/>
</Grid>
where TextChanged is the original event for TextBox
UserControl1.xaml.cs:
public partial class UserControl1 : UserControl
{
public UserControl1()
{
_isFirstTime = true;
DataContext = this;
InitializeComponent();
}
public event TextChangedEventHandler TextBoxTextChanged;
bool _isFirstTime;
//MyText Dependency Property
public string MyText
{
get { return (string)GetValue(MyTextProperty); }
set { SetValue(MyTextProperty, value); }
}
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register("MyText", typeof(string), typeof(UserControl1), new UIPropertyMetadata(""));
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (TextBoxTextChanged != null)
if (!_isFirstTime)
{
TextBoxTextChanged(sender, e);
}
_isFirstTime = false;
}
}
where TextBox_TextChanged is the customized eventHandler for original TextChanged
and TextBoxTextChanged is more like a wrapper for the original TextChanged
Window.xaml:
<Grid>
<c:UserControl1 TextBoxTextChanged="TextBoxValueChanged"/>
</Grid>
as you see you can add an eventHandler to the event wrapper (TextBoxTextChanged)
Window.xaml.cs:
private void TextBoxValueChanged(object sender, TextChangedEventArgs e)
{
MessageBox.Show("asd");
}
finally TextBoxValueChanged won't be fired the first time Text is changed
private void TextBoxValueChanged(object sender, TextChangedEventArgs e)
{
if (Textbox1.IsFocused)
{
App.Current.Properties["TextChanged"] = "1"; // Set Flag
}
}
private void TextBoxLostFocus(object sender, RoutedEventArgs e)
{
if (App.Current.Properties["TextChanged"] == "1")
{
// Do Your Wor Here
App.Current.Properties["TextChanged"] = "0"; // Clear Flag
}
}
On your XAML:
<TextBox xName="TextBox1" LostFocus="TextBoxLostFocus" TextChanged="TextBoxValueChanged"/>
(This is a very rudimentary, dirty, codebehind hack... checking the IsLoaded property as stated by Brent I found to be efficient)
Here since on textbox control creation it's not focused, the TextChanged event will fire but the flag "1" is NOT set...
Later when user leaves field after editing it, since it had focus the Flag is set... the LostFocus is fired, but only runnig code if textbox was changed.
I found a way of preventing this behavior across multiple inputs without having to create a unique bool for each input...
private void TextChanged_UpdateItem(object sender, TextChangedEventArg e)
{
TextBox txtBox = sender as TextBox;
if (!txtBox.IsFocused)
return;
//The rest of your code here
}
So basically, if the text field doesn't have focus (like on initialization) it just returns. This also prevents it from firing if the data is changed elsewhere. :)
Alternatively, as mentioned by Brent, you can just look for "IsLoaded":
private void TextChanged_UpdateItem(object sender, TextChangedEventArg e)
{
TextBox txtBox = sender as TextBox;
if (!txtBox.IsLoaded)
return;
//The rest of your code here
}

MVVM C# WPF binding mouse double click

I want to copy the content of one text box to another text box by clicking the mouse.
How do I bind a mouse click event?
This sample is for RightClick, but you can adjust the event according to your needs:
<TextBox>
<TextBox.InputBindings>
<MouseBinding Gesture="RightClick" Command="{Binding YourCommand}" />
</TextBox.InputBindings>
</TextBox>
Edit: I uploaded on my SkyDrive a sample app that illustrates how to use this method in order to achieve exactly what you need. Please be advised that it will only work for .NET Framework 4+
Want to add a behavior to a control ? Just use the Ramora pattern !
Hope this helps
Use this code for TreeView
<TreeView commandBehaviors:MouseDoubleClick.Command="{Binding YourCommand}"
commandBehaviors:MouseDoubleClick.CommandParameter="{Binding}"
.../>
Use this code for TreeViewItem
<TreeView ItemsSource="{Binding Projects}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="commandBehaviors:MouseDoubleClick.Command"
Value="{Binding YourCommand}"/>
<Setter Property="commandBehaviors:MouseDoubleClick.CommandParameter"
Value="{Binding}"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Use this code to create a new behavior MouseDoubleClick
public class MouseDoubleClick
{
public static DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command",
typeof(ICommand),
typeof(MouseDoubleClick),
new UIPropertyMetadata(CommandChanged));
public static DependencyProperty CommandParameterProperty =
DependencyProperty.RegisterAttached("CommandParameter",
typeof(object),
typeof(MouseDoubleClick),
new UIPropertyMetadata(null));
public static void SetCommand(DependencyObject target, ICommand value)
{
target.SetValue(CommandProperty, value);
}
public static void SetCommandParameter(DependencyObject target, object value)
{
target.SetValue(CommandParameterProperty, value);
}
public static object GetCommandParameter(DependencyObject target)
{
return target.GetValue(CommandParameterProperty);
}
private static void CommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
Control control = target as Control;
if (control != null)
{
if ((e.NewValue != null) && (e.OldValue == null))
{
control.MouseDoubleClick += OnMouseDoubleClick;
}
else if ((e.NewValue == null) && (e.OldValue != null))
{
control.MouseDoubleClick -= OnMouseDoubleClick;
}
}
}
private static void OnMouseDoubleClick(object sender, RoutedEventArgs e)
{
Control control = sender as Control;
ICommand command = (ICommand)control.GetValue(CommandProperty);
object commandParameter = control.GetValue(CommandParameterProperty);
command.Execute(commandParameter);
}
}
It sounds like you are inventing a new behaviour for your textbox :)
I would just consider if the users of your program understands and likes this behaviour.
Maybe it is easier to understand the funcionality if it is just a button you have to click - it is also faster to implement :)
I think you could bind mouse gestures to commands. Take a look at this: http://www.thejoyofcode.com/Invoking_a_Command_on_a_Double_Click_or_other_Mouse_Gesture.aspx
I'm not sure what exactly you're wanting to bind to.
There is no readily available MouseClick event as far as i'm aware.
the Click event as you'd find on a Button is inherited from ButtonBase and is not readily available on most controls.
MouseDoubleClick is inherited from Control and available on anythning deriving from it.
in your example it sounds like a simple Button with its Click event handled might do the trick.
To bind to the click event, you just need to specify the event handler for the event in the Button.
Something like:
XAML:
<TextBox Name=TextBoxOne />
<TextBox Name=TextBoxTwo />
<Button Click="CopyTextButton_Click"/>
And in your code behind:
void CopyTextButton_Click(object sender, RoutedEventArgs e)
{
//Copy the text and anything else you need done
}
Otherwise if this is a more specialised scenario, you might want to investigate using a UserControl or as AndrewS answered above, a Command.
Hope it helps.
You can easily do this by creating a new behavior.
<TextBox
MouseDoubleClick="SelectAddress"
GotKeyboardFocus="SelectAddress"
PreviewMouseLeftButtonDown="SelectivelyIgnoreMouseButton" />
Here's the code behind:
private void SelectAddress(object sender, RoutedEventArgs e)
{
TextBox tb = (sender as TextBox);
if (tb != null)
{
tb.SelectAll();
}
}
private void SelectivelyIgnoreMouseButton(object sender,
MouseButtonEventArgs e)
{
TextBox tb = (sender as TextBox);
if (tb != null)
{
if (!tb.IsKeyboardFocusWithin)
{
e.Handled = true;
tb.Focus();
}
}
}
Please update this snippet according to your need.

WPF TreeView - How to scroll so expanded branch is visible

When I expand items in my treeview so that scrolling is necessary, a scrollbar appears. However, it doesn't scroll down for the newly expanded branch of items - they get cropped by the bottom of the control. So as I continue expanding items at the bottom of the tree, I have to keep manually scrolling down to see the new children. Anyone have a suggestion for how make it automatically scroll to show the newly expanded items?
You can use a simple EventSetter in TreeViewItem style to invoke an event handler when the item is selected. Then call BringIntoView for the item.
<TreeView >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="Selected" Handler="TreeViewSelectedItemChanged" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
private void TreeViewSelectedItemChanged(object sender, RoutedEventArgs e)
{
TreeViewItem item = sender as TreeViewItem;
if (item != null)
{
item.BringIntoView();
e.Handled = true;
}
}
On the TreeView, handle the TreeViewItem.Expanded event (you can do this at the TreeView level because of event bubbling). In the Expanded handler, call BringIntoView on the TreeViewItem that raised the event.
You may need a bit of trial and error to get hold of the TreeViewItem in your event handler code. I think (haven't checked) that the sender argument to your Expanded event handler will be the TreeView (since that's where the event handler is attached) rather than the TreeViewItem. And the e.Source or e.OriginalSource may be an element in the TreeViewItem's data template. So you may need to use VisualTreeHelper to walk up the visual tree to find the TreeViewItem. But if you use the debugger to inspect the sender and the RoutedEventArgs this should be trivial to figure out.
(If you're able to get this working and want to bundle it up so you don't have to attach the same event handler to every TreeView, it should be easy to encapsulate it as an attached behaviour which will allow you to apply it declaratively, including via a Style.)
Use a dependency property on an IsSelected trigger:
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="commands:TreeViewItemBehavior.BringIntoViewWhenSelected" Value="True" />
</Trigger>
</Style.Triggers>
Here's the code for the dependency property:
public static bool GetBringIntoViewWhenSelected(TreeViewItem treeViewItem)
{
return (bool)treeViewItem.GetValue(BringIntoViewWhenSelectedProperty);
}
public static void SetBringIntoViewWhenSelected(TreeViewItem treeViewItem, bool value)
{
treeViewItem.SetValue(BringIntoViewWhenSelectedProperty, value);
}
public static readonly DependencyProperty BringIntoViewWhenSelectedProperty =
DependencyProperty.RegisterAttached("BringIntoViewWhenSelected", typeof(bool),
typeof(TreeViewItemBehavior), new UIPropertyMetadata(false, OnBringIntoViewWhenSelectedChanged));
static void OnBringIntoViewWhenSelectedChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
TreeViewItem item = depObj as TreeViewItem;
if (item == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool)e.NewValue)
item.BringIntoView();
}
Thanks to itowlson's answer, here's the expanded event handler code that works for both of my trees
private static void Tree_Expanded(object sender, RoutedEventArgs e)
{
// ignore checking, assume original source is treeviewitem
var treeViewItem = (TreeViewItem)e.OriginalSource;
var count = VisualTreeHelper.GetChildrenCount(treeViewItem);
for (int i = count - 1; i >= 0; --i)
{
var childItem = VisualTreeHelper.GetChild(treeViewItem, i);
((FrameworkElement)childItem).BringIntoView();
}
// do NOT call BringIntoView on the actual treeviewitem - this negates everything
//treeViewItem.BringIntoView();
}
I modified Jared's answer in combination with the strategy from here: https://stackoverflow.com/a/42238409/2477582
The main advantage is that there aren't n calls of BringIntoView() for n childs. There is only one call of BringIntoView for an area that covers all of the child's heights.
Additionally, the purpose of the referred topic is realized as well. But this part may be removed, if unwanted.
/// <summary>Prevents automatic horizontal scrolling, while preserving automatic vertical scrolling and other side effects</summary>
/// <remarks>Source: https://stackoverflow.com/a/42238409/2477582 </remarks>
private void TreeViewItem_RequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
{
// Ignore re-entrant calls
if (m_SuppressRequestBringIntoView)
return;
// Cancel the current scroll attempt
e.Handled = true;
// Call BringIntoView using a rectangle that extends into "negative space" to the left of our
// actual control. This allows the vertical scrolling behaviour to operate without adversely
// affecting the current horizontal scroll position.
m_SuppressRequestBringIntoView = true;
try
{
TreeViewItem tvi = sender as TreeViewItem;
if (tvi != null)
{
// take care of children
int ll_ChildCount = VisualTreeHelper.GetChildrenCount(tvi);
double ll_Height = tvi.ActualHeight;
if (ll_ChildCount > 0)
{
FrameworkElement ll_LastChild = VisualTreeHelper.GetChild(tvi, ll_ChildCount - 1) as FrameworkElement;
ll_Height += ll_ChildCount * ll_LastChild.ActualHeight;
}
Rect newTargetRect = new Rect(-1000, 0, tvi.ActualWidth + 1000, ll_Height);
tvi.BringIntoView(newTargetRect);
}
}
catch (Exception ex)
{
m_Log.Debug("Error in TreeViewItem_RequestBringIntoView: " + ex.ToString());
}
m_SuppressRequestBringIntoView = false;
}
The above solution works together with this:
/// <summary>Correctly handle programmatically selected items (needed due to the custom implementation of TreeViewItem_RequestBringIntoView)</summary>
/// <remarks>Source: https://stackoverflow.com/a/42238409/2477582 </remarks>
private void TreeViewItem_Selected(object sender, RoutedEventArgs e)
{
((TreeViewItem)sender).BringIntoView();
e.Handled = true;
}
This part takes care of toggling the elements at each click:
/// <summary>Support for single click toggle</summary>
private void TreeViewItem_MouseUp(object sender, MouseButtonEventArgs e)
{
TreeViewItem tvi = null;
// Source may be TreeViewItem directly, or be a ContentPresenter
if (e.Source is TreeViewItem)
{
tvi = e.Source as TreeViewItem;
}
else if (e.Source is ContentPresenter)
{
tvi = (e.Source as ContentPresenter).TemplatedParent as TreeViewItem;
}
if (tvi == null || e.Handled) return;
tvi.IsExpanded = !tvi.IsExpanded;
e.Handled = true;
}
Finally the XAML part:
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<EventSetter Event="RequestBringIntoView" Handler="TreeViewItem_RequestBringIntoView" />
<EventSetter Event="Selected" Handler="TreeViewItem_Selected" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
A simple event listener on the tree worked for me:
<TreeView Margin="10,40,10,10" Grid.Column="0" x:Name="treeView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" SelectedItemChanged="TreeView_SelectedItemChanged" />
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) {
if (e.NewValue == null)
return;
((TreeViewItem)e.NewValue).BringIntoView();
}

Resources