wpf treeviewitem - wpf

I am creating a TreeView using the following method looping through an xml document.
However when any TreeViewItem is selected all the nodes in the hierarchy are getting the event triggers instead of just the selected TreeViewItem.
For example let's say we select the grandchild of a node. All the nodes including grandchild, child, parent are triggering the same event.
In other words we would expect only the grandchild trigger the associated event whereas and the event should get called only once but it ends up being called 3 times for all the nodes of the hierarchy of the selected item.
Here is the code:
TreeViewItem getTreeViewItemWithHeader(XmlNode node)
{
TreeViewItem tvi = new TreeViewItem();
tvi.Header = node.Name;//hdr;
tvi.Tag = node.Attributes["Tag"].Value;
tvi.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(tvi_PreviewMouseLeftButtonDown);
tvi.Selected += new RoutedEventHandler(tvi_Selected);
return tvi;
}
Please let me know if you have any suggestions, thanks
N

This is working correctly. The PreviewMouseLeftButtonDown event is a routed event (in this case the strategy is tunneling). This means that the root of the visual tree gets the event first, and it works its way down until it reaches the control that originally triggered the event. The MouseLeftButtonDown and Selected events is also routed, but its strategy is bubbling - this means that the event works its way up the visual tree, starting with the control that triggered the event.
If you want a routed event to not continue to be sent, set the Handled property of the RoutedEventArgs passed in to true.

Related

Master-detail: How to fetch a control from a template inside the "detail" ContentControl?

I have a ListView (on the 'master' side) whose selection drives a ContentControl's Content property (on the 'detail' side). The ContentControl's visual tree comes from either of two DataTemplate resources that use DataType to choose which detail view to render based on what is selected in the ListView.
That part works fine.
The part I'm struggling with is that there is a particular control inside (one of) the templates that I need to obtain a reference to whenever it changes (e.g. the template selected changes or the ListView selection changes such that the instance of the control is recreated.)
In my ListView.SelectionChanged event handler, I find the ContentControl has not yet been updated with its new visual tree, so initially it's empty on the first selection, and for subsequent selections its visual tree matches the old selection instead of the new one.
I've tried delaying my code by scheduling on the Dispatcher with a priority as low as DispatcherPriority.Loaded, which works for the first selection but on subsequent selections my code still runs before the visual tree is updated.
Is there a better event I should be hooking to run whenever the ContentControl's visual tree is changed to reflect a changed data-bound value to its Content property?
Extra info: the reason I need to reach into the expanded DataTemplate is that I need to effectively set my view model's IList SelectedItems property to a DataGrid control's SelectedItems property. Since DataGrid.SelectedItems is not a dependency property, I have to do this manually in code.
The fix required a combination of techniques. For the first selection that populates the visual tree, I needed to handle ContentControl.OnApplyTemplate() which is only a virtual method rather than an event. I derived from it and exposed it as an event:
public class ContentControlWithEvents : ContentControl
{
public event EventHandler? TemplateApplied;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.TemplateApplied?.Invoke(this, EventArgs.Empty);
}
}
In the XAML I used the above class rather than ContentControl:
<local:ContentControlWithEvents
Content="{Binding SelectedAccount}"
x:Name="BankingSelectedAccountPresenter"
TemplateApplied="BankingSelectedAccountPresenter_TemplateApplied" />
Then I handle the event like this:
void BankingSelectedAccountPresenter_TemplateApplied(object sender, EventArgs e) => this.UpdateSelectedTransactions();
private void UpdateSelectedTransactions()
{
if (this.MyListView.SelectedItem?.GetType() is Type type)
{
DataTemplateKey key = new(type);
var accountTemplate = (DataTemplate?)this.FindResource(key);
Assumes.NotNull(accountTemplate);
if (VisualTreeHelper.GetChildrenCount(this.BankingSelectedAccountPresenter) > 0)
{
ContentPresenter? presenter = VisualTreeHelper.GetChild(this.BankingSelectedAccountPresenter, 0) as ContentPresenter;
Assumes.NotNull(presenter);
presenter.ApplyTemplate();
var transactionDataGrid = (DataGrid?)accountTemplate.FindName("TransactionDataGrid", presenter);
this.ViewModel.Document.SelectedTransactions = transactionDataGrid?.SelectedItems;
}
}
}
Note the GetChildrenCount check that avoids an exception thrown from GetChild later if there are no children yet. We'll need that for later.
The TemplateApplied event is raised only once -- when the ContentControl is first given its ContentPresenter child. We still the UpdateSelectedTransactions method to run when the ListView in the 'master' part of the view changes selection:
void BankingPanelAccountList_SelectionChanged(object sender, SelectionChangedEventArgs e) => this.UpdateSelectedTransactions();
On initial startup, SelectionChanged is raised first, and we skip this one with the GetChildrenCount check. Then TemplateApplied is raised and we use the current selection to find the right template and search for the control we need. Later when the selection changes, the first event is raised again and re-triggers our logic.
The last trick is we must call ContentPresenter.ApplyTemplate() to force the template selection to be updated before we search for the child control. Without that, this code may still run before the template is updated based on the type of item selected in the ListView.

WPF Control Loaded Event fires before children are created

I have an event handler for the "Loaded" event of a control in which I alter the properties of the one of the visual children. For years this has worked fine for me; My handler gets the child of the type it needs, alters the padding and all is well.
But now I've started using this control on a page of a TabControl. The page is one of several I create at once and it is not initially showing. For this page, the Loaded event fires before the user activates the page. But when my Loaded handler is called, there are literally zero visual children. The VisualTreeHelper reports that the count of children is 0.
Yet later on when click on the tab and make the page visible, I can see the control and all of its children have been created. So apparently they've been created after the Loaded event is fired.
Is there some other event I can hook on to to be sure that a control's children have been created?
This is the XAML I use
<tk:RadColorPicker
Margin="5,0"
SelectedColor="{Binding Settings.LiveImageHighlightColor, Mode=TwoWay}"
Loaded="RadColorPicker_OnLoaded"/>
And this is the handler:
private void RadColorPicker_OnLoaded(object sender, RoutedEventArgs e)
{
if (!(sender is RadColorPicker rcp))
return;
var count = VisualTreeHelper.GetChildrenCount(rcp);
// Other code here where I get the child of the type I want and change its padding
// But it's irrelevant to this question because the count = 0 in the error case
}
I've read the MS guidance on this and several threads on SO. They all point to "Loaded" as the event I want

How can I block event in root element and let the event fired in the container(parent) level?

I'm working with WPF.
My visual tree hierarchy as the following:
RadDiagram > RadDiagramShape > MyControl
The content of RadDiagramShape is mycontrol.
In myControl class, I have handled (MouseLeftButtonDown) Event, I put drag-drop code inside it(which I need it in another place). So, it's Direct Event not Tunneling or Bubbling!
While I'm moving my Custom Control which is the content of the RadDiagramShape, in RadDiagram, it doesn't move (It's trying to be dragged) because the MouseLeftButtonDown has been handled inside MyControl.
It prevents the event from bubbling up the Visual tree, preventing any parent handlers from being notified of the event.
I tried to handled the Event for the RadDiagramShape and for the RadDiagram as e.Handled = true;
but it did nothing because It's MouseLeftButtonDown and it's handled inside the root element so, it won't bubble or tunnel and I didn't override movement code, which I don't want to override it. Because I tried it before and it didn't give me the same slightly move that built-in in WPF.
How can I block MouseLeftButtonDown event in root element and let the event fired in the container(parent) level?
Please check in your control handler if 'OriginalSource' is the same control that you want or not.
if ((e.OriginalSource is TextBox) && (e.OriginalSource as TextBox).Name == "TextBoxName")
{
//Do every thing you want
}
Thank you leila karimi. You gave me the orientation, the condition itself didn't work. But I put another condition and it worked
void MyLabel_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
MyLabel dc = (MyLabel)sender;
if (dc.Parent.GetType() != typeof(RadDiagramShape))
{///....drop it}
}

Preventing WPF TreeView's GotFocus event from bubbling up the tree

I'm trying to write an event handler that fires every time a node in a TreeView gets the focus. The problem I'm running into is that the event handler fires on the TreeViewItem (node) that I click on with the mouse, and then it continues to bubble up the control tree, even though I've set e.Handled = true on the RoutedEventArgs provided to the handler. Does anybody have an idea what the problem could be ? I've double checked my code and I can see no reason why this should be happening.
Are you using TreeView.GotFocus when you really want TreeViewItem.Selected?
<TreeView TreeViewItem.Selected="treeView1_Selected" />
If you really want focus, use TreeViewItem.Focus instead so that items are targeted instead of the whole tree.
<TreeView TreeViewItem.GotFocus="treeView1_GotFocus"/>

What's the best way to auto-scroll a list view to the last added item?

I use a ListView to show a list of errors as they occur in my application. It behaves and looks exactly like the Error List in Visual Studio. I want to add auto-scrolling when the last error item is selected (like how Visual Studio's Log Window auto-scrolls when you place the caret at the end).
The list of errors is in an ObservableCollection, which is passed to the ListView.ItemsSource like this:
public ObservableCollection<ErrorListItem> Items;
...
MyListView.ItemsSource = _Items;
I tried performing the auto-scroll in the _Items_CollectionChanged event handler, but because this is the event on the ItemsSource and not on the actual ListViewItems, it's a pain to figure out if the last item is selected, select the new row, etc. It's especially hard since it seems the ListViewItems are not created instantly. I managed to make it auto-scroll by delaying the call to set the last item selected like this:
void _Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// determine the last item to select from 'e'
...
_ItemPendingToBeScrolled = newItemToSelect;
ListView.SelectedItem = newItemToSelect;
Dispatcher.BeginInvoke(DispatcherPriority.Background,
(ThreadStart)delegate
{
if (_ItemPendingToBeScrolled != null)
{
ListView.ScrollIntoView(_ItemPendingToBeScrolled);
ItemPendingToBeScrolled = null;
}
})
}
But that's obviously not the right way to do it. Also, I want things to keep working if the list is filtered (not checking the last item in my source, but the last ListViewItem in the ListView).
Is there a way to listen to events when a ListViewItem gets added to the ListView following an addition to the bound collection? That would be the ideal event to capture in order to properly do my auto-scrolling. Or is there another technique I could use?
I have a lot of issues with listboxes/listviews and their scrolling, however, you mentioned hooking to the listview's changed event, is it because you can't listen to the observable collection's CollectionChanged event? ObservableCollection is way more stable than List controls, and you'll get the same notifications.
You can also bubble these events up if it's not working in the UI and you don't have access, this way you treat your scrolling in the UI without having access to the actual collection, just keep a reference to the Selected Item in your custom EventArgs class

Resources