Why e.Source depends on TreeView populating method? - wpf

I have two trees:
fooTree - made up of elements,
barTree - constructed by
Both trees have MouseRightButtonDown event, but the e.Source type differs:
fooTree - System.Windows.Controls.TreeViewItem
barTree - System.Windows.Controls.TreeView
Why e.Source differs? Also, how can I get the clicked item for the barTree?
Markup:
<TreeView Name="fooTree" MouseRightButtonDown="fooTree_MouseDown">
<TreeViewItem Header="foo"></TreeViewItem>
<TreeViewItem Header="foo"></TreeViewItem>
</TreeView>
<TreeView Name="barTree" MouseRightButtonDown="barTree_MouseDown" ItemsSource="{Binding BarItems}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TextBlock Text="{Binding}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Code:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DataContext = this;
}
public string[] BarItems
{
get { return new string[] { "bar", "bar" }; }
}
private void barTree_MouseDown(object sender, MouseButtonEventArgs e)
{
}
private void fooTree_MouseDown(object sender, MouseButtonEventArgs e)
{
}
}

Don't know why this happens, but at least I have found a solution:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/f0d3af69-6ecc-4ddb-9526-588b72d5196b/
If your handler is on the TreeView, use the OriginalSource property in the
event arguments and walk up the visual
parent chain until you find a
TreeViewItem. Then, select it. You can
walk the visual parent chain by using
System.Windows.Media.VisualTreeHelper.GetParent.
You could try registering a class handler for type TreeViewItem and the
mouse down event. Then, your handler
should only be called when mouse
events pass through TreeViewItem
elements.
You could register a class handler for type TreeViewItem and the context
menu opening event.
So my code is:
private void OnMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
TreeViewItem treeViewItem = VisualUpwardSearch<TreeViewItem>(e.OriginalSource as DependencyObject) as TreeViewItem;
}
static DependencyObject VisualUpwardSearch<T>(DependencyObject source)
{
while (source != null && source.GetType() != typeof(T))
source = VisualTreeHelper.GetParent(source);
return source;
}

You can get the clicked item in the bartree using:
((e.Source) as TreeView).SelectedValue
But be aware that the item must actually selected first (using leftMouse). The item is not immediately selected using rightMouse...

Related

Accessing to a ListView at runtime to update an item

I have to update a ListView item by clicking on a button. How do I find and update one at the runtime?
update: I mean I have to find the certain ListView item and update the text of this item only.
When ListViewItems were added to the ListView manually, you can look them up by their content and replace with new content like this (using System.Linq):
object contentToReplace = ...;
object newContent = ...;
ListViewItem item = listView.Items.Cast<ListViewItem>().FirstOrDefault(
lvi => lvi.Content == contentToReplace);
if (item != null)
{
item.Content = newContent;
}
You may use commands. For example:
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public static readonly ICommand ItemClickCommand = new RoutedCommand("ItemClick", typeof(MainWindow));
public MainWindow()
{
InitializeComponent();
this.CommandBindings.Add(
new CommandBinding(
MainWindow.ItemClickCommand,
this.ExecuteItemClickCommand,
this.CanExecuteItemClickCommand));
}
private void CanExecuteItemClickCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = e.Parameter is ListBoxItem;
}
private void ExecuteItemClickCommand(object sender, ExecutedRoutedEventArgs e)
{
// Here you can access ListBoxItem that holds a clicked button.
ListBoxItem listBoxItem = (ListBoxItem)e.Parameter;
listBoxItem.Content = "...";
}
}
}
Now, the only thing you need is to assign ItemClickCommand to a button and bind CommandParameter to corresponding ListBoxItem.
XAML example:
<Window ...
xmlns:local="clr-namespace:WpfApplication1">
<ListBox>
<ListBoxItem>
<ListBoxItem.Content>
<Button Command="{x:Static local:MainWindow.ItemClickCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Content="Click Me"/>
</ListBoxItem.Content>
<...>

How can i receive a reference to a control in WPF (MVVM)?

In my WPF MVVM Project I have a button that triggers a function that should add a node to a xml and then set the focus to a textbox.
My question is, how can i receive a reference to a control?
View:
<Button Command="{Binding Path=ButtonAddCategory_Click}" />
ViewModel:
RelayCommand buttonAddCategory_Click;
public ICommand ButtonAddCategory_Click
{
get
{
return buttonAddCategory_Click ?? (buttonAddCategory_Click = new RelayCommand(param => this.AddCategory(),
param => true));
}
}
public void AddCategory()
{
...
//get the "node" -> reference?
XmlNode selectedItem = (XmlNode)treeView.SelectedItem;
..
//add the node to the xml
..
//change focus -> reference?
textBoxTitel.Focus();
textBoxTitel.SelectAll();
}
Don't do it in the ViewModel. The ViewModel shouldn't know anything about the view.
You can do it in code-behind:
handle the TreeView.SelectedItemChanged event in code-behind, and update a SelectedItem property on the ViewModel (you could also do it with an attached behavior)
to focus the TextBox, raise an event from the ViewModel and handle it in code-behind:
ViewModel:
public XmlNode SelectedItem { get; set; }
public event EventHandler FocusTitle;
public void AddCategory()
{
...
//get the "node" -> reference?
XmlNode selectedItem = this.SelectedItem;
..
//add the node to the xml
..
// Notify the view to focus the TextBox
if (FocusTitle != null)
FocusTitle(this, EventArgs.Empty);
}
Code-behind:
// ctor
public MyView()
{
InitializeComponent();
DataContextChanged += MyView_DataContextChanged;
}
private void MyView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
MyViewModel vm = (MyViewModel)e.NewValue;
vm.FocusTitle += ViewModel_FocusTitle;
}
private void TreeView1_SelectedItemChanged(object sender, RoutedPropertyChangedEventHandler<Object> e)
{
MyViewModel vm = (MyViewModel)DataContext;
vm.SelectedItem = (XmlNode)e.NewValue;
}
private void ViewModel_FocusTitle(object sender, EventArgs e)
{
textBoxTitle.Focus();
}
You could use the FocusManager.FocusedElement attached property to handle ensuring the TextBox receives focus.
<DataTemplate DataType="{x:Type YourViewModel}">
<Grid FocusManager.FocusedElement="{Binding ElementName=userInput}">
<TextBox x:Name="userInput" />
</Grid>
</DataTemplate>
As for your second part (textBox.SelectAll()) you may have to work on a behavior or attached property of your own that handles the focusing and selecting in one fell swoop.

WPF ComboBox, whenever bound data changed, set SelectedIndex to 0?

I couldn't find the right event to achieve the functionality.
TargetUpdated event didn't work.
setting SelectedIndex to 0 on xaml would only affect the first load of data.
You can:
Set NotifyOnTargetUpdated on the binding
Add an event handler for Binding.TargetUpdated
In that event handler register for ItemsSource.CollectionChanged
In that event handler set the selected index to zero
The issue is most likely that you didn't set NotifyonTargetUpdated in the binding so the first event wasn't fired or that the collection was being updated but it was the same collection so the second event is necessary.
Here's a working example using a ListBox as the ItemsControl and a MessageBox as a proxy for doing whatever you want to do when the event fires.
Here is the markup:
<Grid>
<DockPanel>
<Button DockPanel.Dock="Top" Content="Update Target" Click="ButtonUpdateTarget_Click"/>
<Button DockPanel.Dock="Top" Content="Update Item" Click="ButtonUpdateItem_Click"/>
<ListBox Name="listBox" Binding.TargetUpdated="ListBox_TargetUpdated" ItemsSource="{Binding Items, NotifyOnTargetUpdated=True}"/>
</DockPanel>
</Grid>
and here is the code-behind:
public class ViewModel : INotifyPropertyChanged
{
ObservableCollection<string> items;
public ObservableCollection<string> Items
{
get { return items; }
set { items = value; OnPropertyChanged("Items"); }
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
void SetDataContext()
{
DataContext = viewModel;
viewModel.Items = new ObservableCollection<string> { "abc", "def", "ghi" };
}
ViewModel viewModel = new ViewModel();
private void ButtonUpdateTarget_Click(object sender, RoutedEventArgs e)
{
viewModel.Items = new ObservableCollection<string> { "xyz", "pdq" };
}
private void ButtonUpdateItem_Click(object sender, RoutedEventArgs e)
{
viewModel.Items[0] = "xxx";
}
private void ListBox_TargetUpdated(object sender, DataTransferEventArgs e)
{
MessageBox.Show("Target Updated!");
(listBox.ItemsSource as INotifyCollectionChanged).CollectionChanged += new NotifyCollectionChangedEventHandler(listBox_CollectionChanged);
}
void listBox_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
MessageBox.Show("Item Updated!");
}
Have you tried the SourceUpdated event?
I faced same problem. To overcome this problem, I used the following steps:
create a TextBox
Set visibility of TextBox to Collapsed
Bind Text to ListBox.Items.Count
<TextBox x:Name="txtCount" TextChanged="TextBox_TextChanged" Text="{Binding ElementName=ListBox1, Path=Items.Count, Mode=OneWay}" Visibility="Collapsed" />
In the TextBox_TextChanged event, set SelectedIndex to 0
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
int count = 0;
if(int.TryParse(txtCount.Text,out count) && count>0)
ListBox1.SelectedIndex = 0;
}

WPF datagrid selected row clicked event ?

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;
}

Create Event Handler for TreeViewItem in WPF

Im adding items to TreeView control via ItemsSource property and ItemTemplate property to set the template for TreeViewItem. How can i add an event handler to handle selection change event on TreeViewItems?
For now my ItemTemplate looks like this:
<Window.Resources><DataTemplate x:Key="PeerDetailTemplate">
<TextBlock Text="{Binding DESCRIPTION}" Tag="{Binding ID}" GotFocus="GetModules"/>
</DataTemplate></Window.Resources>
But it doesnt work (GetModules is not called). Im new to WPF, so show me the right direction to do such things, please.
If you want to capture the SelectedItemChanged event in a TreeView, then you need to set the event handler on the parent node, i.e.,
XAML
<StackPanel>
<TreeView SelectedItemChanged="OnTreeViewSelectedItemChanged">
<TreeViewItem Header="Desktop">
<TreeViewItem Header="Computer" />
<TreeViewItem Header="My Documents" />
<TreeViewItem Header="c:\" />
</TreeViewItem>
<TreeViewItem Header="Recyle Bin" >
<TreeViewItem Header="foo.txt" />
<TreeViewItem Header="bar.txt" />
<TreeViewItem Header="fizz.buzz" />
</TreeViewItem>
<TreeViewItem Header="Control Panel" >
<TreeViewItem Header="Programs" />
<TreeViewItem Header="Security" />
<TreeViewItem Header="User Accounts" />
</TreeViewItem>
</TreeView>
<TextBlock Margin="20" x:Name="MyTextBlock" />
</StackPanel>
Code Behind:
private void OnTreeViewSelectedItemChanged( object sender, RoutedPropertyChangedEventArgs<object> e )
{
MyTextBlock.Text = ( (TreeViewItem) ( (TreeView) sender ).SelectedItem ).Header.ToString();
}
You'll need to add an event handler to the TreeView's SelectedItemChanged event.
<TreeView x:Name="myTreeView"
SelectedItemChanged="myTreeView_SelectedItemChanged"
ItemTemplate="{StaticResource PeerDetailTemplate} />
Since this is fired after the selection is changed, you can use the TreeView's selected item property to access the tree view item:
private void myTreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
TreeViewItem selectedItem = (TreeViewItem)myTreeView.SelectedItem;
// do stuff
}
Selection and Selection and focus are two different concepts. It sounds like you're interested in selection, which in this case is a property of the TreeView. Event TreeView.SelectedItemChanged will notify you of selection changes and property TreeView.SelectedItem will tell you what is selected.
There are different ways to Bind of the SelectedItemChanged event of TreeviewItem:
Method 1: Direct Attaching Event
The attachment of the event can be done in Xaml
<TreeView x:Name="treeview1" HorizontalAlignment="Left" Height="243" Margin="30,211,0,0" VerticalAlignment="Top" Width="667" SelectedItemChanged="TreeView_SelectedItemChanged">
<TreeViewItem Header="TreeViewItem"/>
</TreeView>
Private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
}
Or on code Behind
myTreeview.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(TreeView_SelectedItemChanged);
private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
}
Method 2 Usage of Extended Class
public class ExtendedTreeView : TreeView
{
public ExtendedTreeView()
: base()
{
this.SelectedItemChanged += new RoutedPropertyChangedEventHandler<object>(TreeView_SelectedItemChanged);
}
void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
if (SelectedItem != null)
{
SetValue(SelectedItem_Property, SelectedItem);
}
}
public object SelectedItem_
{
get { return (object)GetValue(SelectedItem_Property); }
set { SetValue(SelectedItem_Property, value); }
}
public static readonly DependencyProperty SelectedItem_Property = DependencyProperty.Register("SelectedItem_", typeof(object), typeof(ExtendedTreeView), new UIPropertyMetadata(null));
}
Next just create the Treeview with the new custom class object.
ExtendedTreeView myTreeview = new ExtendedTreeView();
Method 3 Usage of Behavior
public class BindableSelectedItemBehavior : Behavior<TreeView>
{
#region SelectedItem Property
public object SelectedItem
{
get { return (object)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var item = e.NewValue as TreeViewItem;
if (item != null)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (this.AssociatedObject != null)
{
this.AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
}
Next just attach the Treeview to the Behavior
On Xaml file
<TreeView>
<e:Interaction.Behaviors>
<behaviours:BindableSelectedItemBehavior SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
</e:Interaction.Behaviors>
</TreeView>
Or on Code Behind
BindableSelectedItemBehavior selectedItemBehavior = new BindableSelectedItemBehavior();
System.Windows.Interactivity.Interaction.GetBehaviors(treeview1).Add(selectedItemBehavior);
Cordially

Resources