Can I make this easier? Bubbling vs Tunneling in WPF? - c#

I am working with drag and drop functionality in a WPF application.
I have a tree structure such as the following.
StackPanel
- Border 1
- Grid A
- Grid 1
- Grid 2
- Border 2
- Grid B
- Grid 1
- Grid 2
This is my attempt to show the tree representation of the UIElements in my WPF application.
so the stack panel contains Grids with nested grids within it. These are being defined dynamically in code, and not in XAML.
I have the inner Grids(1 & 2) handling Drag and Drop events. The problem I am having is that when a drop operation is being performed, and it is dropped on the border of one of the grids, it falls through and hits the StackPanel Drop. What I would like to do is when an item is dropped on the border, I would like it to fall through to the same drop event handler.
I am fairly new to the idea of Routed Events and Bubbling and Tunneling events. I am aware that I need this event to travel down the element hierarchy which makes me believe that I am in need for "Tunneling" Event. Does this mean that I am supposed to attach the border to an event handler. Right now I have the following code for the Grids to be subscribed to the event.
fieldItemGrid.PreviewDrop += Grid_Drop;
private void Grid_Drop(object sender,DragEventArgs e)
{
//React to Drop Event Here
}
Am I able to do something simpler then rewriting the code that goes here in a Border_Drop Event?
Thanks ahead of time.

I think it would be best to add a very tight (no padding) container (e.g., a Grid) around the area that should act as a dropzone. Then use the tight container to handle the drop.
this way it doesn't matter what borders are specified. Only a padding of the tight container or a margin on the child controls will then mess it up.

The way routing working the preview event starts at the top of the tree until it reaches the element that initiated the event and then the normal event starts at the element itself and works towards the top of the tree.
In your situation, if border is the drop target, the sequence goes this (if event goes unhandled):
PreviewDrop -> StackPanel
PreviewDrop -> Border
Drop -> Border
Drop -> StackPanel
so the event will never reach any of the inner grids when the drop occurs on an element outside the grids. In other words, it will never tunnel into any children of the drop target because the tunnel stops where the drop occurred. It makes sense, how would it know which child was the right child to tunnel into if there were more than one?
As a result, you have to install the drop event handler on the largest element that visually represents the conceptual drop target. You can either switch the order of "Border 1" and "Grid A" or move your drop event handler for "Grid A" to "Border 1".

Related

WPF Datagrid in ScrollView Captures Row Details ComboBox Scrolling events

I have a datagrid inside a scrollviewer. The rows of my datagrid can be expanded to show the details and edit the row items.
In order for the "page" to scroll when the number of rows/row details view pushes the grid too long, I am handling the PreviewMouseWheel event on the datagrid. This works until I have a combobox in my row details with a droplist that has enough items that it also needs to scroll.
When using the mouse wheel, the droplist will scroll, but so does the outer datagrid, effectively scrolling the contents "behind" the droplist and leaving the droplist in the wrong place.
My PreviewMouseWheel event handler does the following:
private void StampPartsDatagrid_PreviewMouseWheelForScrolling(object sender, MouseWheelEventArgs e)
{
StampingScrollViewer.ScrollToVerticalOffset(StampingScrollViewer.VerticalOffset - e.Delta / 3);
}
I tried to trap the dropdown/drop up event on the combo, and while that works, if I move the mouse outside the drop list and scroll with the wheel, the page doesn't scroll.
I looked through many articles on MSDN and SO but they were mostly about getting the datagrid to scroll when inside a scrollviewer.
Is there a way to do a hit test on the Preview scrolling event? Should I trap the mouse/enter leave on the droplist somehow? Should I be handling my scrolling differently?
Thanks
* EDIT *
I resolved this issue. I was overthinking the mouse wheel behaviour. By looking at the Windows settings panel page for regions, which has a very long combo list to display, I saw that if the combo list was dropped the mousewheel only applied to he dropped list. Moving the mouse outside the droplist and scrolling with the wheel has no effect.
Given this, I adopted the same behaviour by trapping the drop open /drop close events and controlling the outer scrolling. My new event handler, taken from this post (https://social.msdn.microsoft.com/Forums/vstudio/en-US/6fc503a6-ba53-4395-b9b8-f56301efd097/mousescroll-of-combobox-scrolls-the-page-as-well?forum=wpf) is this:
private void StampPartsDatagrid_PreviewMouseWheelForScrolling(object sender, MouseWheelEventArgs e)
{
if (!bDetailsComboDropped)
{
StampingScrollViewer.ScrollToVerticalOffset(StampingScrollViewer.VerticalOffset - e.Delta / 3);
}
}
If you don't like to have an bDetailsComboDropped field, you can also try to determine where the preview mouse wheel routed event is coming from. Specifically, check e.OriginalSource to get a reference to the WPF element that was actually triggering the event (or e.Source if you find it better for the purpose).
Combine this with a recursive function using VisualTreeHelper.GetParent to check whether any of the parent elements is a combo box part, or it ends with your root scroll viewer.
(Note: your solution with the Boolean flag might actually provide higher performance, I just posted this in case someone needs a source-oriented solution, e.g. if there were many other controls to check against scrolling internally, etc.)

Disable cross-slide selection ListView

I would like to disable the cross-slide selection behavior for items in my ListView. I am aware of the property IsSwipeEnabled="False" however I still want to allow the user to drag and drop items into another Grid. I also tried capturing the Manipulation started / completed events with the following code.
MyListView.ManipulationMode = ManipulationModes.TranslateRailsX;
MyListView.ManipulationStarted += OnManipulationStarted;
MyListView.ManipulationCompleted += OnManipulationCompleted;
The appropriate events are caught in both handlers however I do not know what actions I should do to disable the selection to occur. I tried a similar approach I did for disabling right-clicking where I set the RoutedEvents.Handled to true but this does not prevent the selection from happening. Is there a clean way to handle this event and disable the selection to happen?
I can think of two things to try
Handle SelectionChanged events and deselect whatever got selected while otherwise allowing drag & drop to work naturally.
Do the above + disable swipe selection and handle drag & drop manually - set MyListView.ManipulationMode = ManipulationModes.TranslateRailsX | ManipulationModes.System, handle manipulation events to initiate and handle drag & drop inputs, call CancelDirectManipulations() on the dragged item container when you decide to switch from pan/scroll to drag&drop interaction, render a dragged item indicator in an adorner layer (just a panel on top of the ListView with some element attached to your finger), then handle drop on the drop target element.

How to disable the capture of a Click event

Using VS2010 on .NET v4 I have the following problems.
I have created two custom Controls, Cell and Board and I want to use the Cell control to capture a number of events and to display some data. The most important event I need to capture is the Click event.
When the custom control has no child control items this is not a problem.
At the level wher Cell is used I can bind an event handler to the Click event with no problem.
public partial class Cell : UserControl
{
....
}
public partial class Board : UserControl
{
private void InitializeComponent()
{
...
this.Cell99.Click += new System.EventHandler(this.Cell_Click);
...
}
}
The Cell control is rather small (about 50x50 pixels in size) and one of the things I want it to display is an integer value as big as possible (about 50 x 50 pixes in size ;-) No problem in doing that, I want to use a Label control for that.
As soon as I add another (e.g. Label) control to Cell it no longer can see some events because the child event is getting 'first rights' on it. For the pixels that are covered by the Label control it will capture the Click event that I want to handle with the Cell control and not with the Label contol.
If it was only one control that I want to use in Cell the solution would be to link the event from the Label control to the Cell control. But I have at least 10 different (Label) controls that need to go in Cell to display all the information I have. Forwarding all the events for all the controls to its parent looks like a bit of a hassle to me. (Except from being prone to errors and other bugs.)
What is the propper way of disabling event capturing for child controls in a user control?
As said I have thought of forwarding events from child to parent but rejected that for now.
Then I have thought of not using controls to display the data but generate a bitmap with the information in it and use that as a background. I'm... not looking forward to that!
And finaly I came with an idea of putting one extra control, on top of all the others, and forward the events it's captures to the parent. That might be the simplest way to continue.
Somehow I thing there must be a better way to approach this problem but how?
Any advise is welcome.
Implement a label class which inherits from Windows.Forms.Label. In this derived label/component class add a method or mechanism to forward any click event received by the label to the parent Cell control. Any labels which are added as child controls of a Cell would be of this custom label class and would all forward their Click events to the parent without having to manually do so for each label instance.

How to get control with lower zindex when mouse clicked in wpf?

I've several controls in the same canvas and may one be covered by another. They are all with same zIndex, but for the order loaded, some being up and others down.
My Question if Acontrol is over Bcontrol, and I click on them, but only A gets the click event. How can I make B get the event, too? Thanks.
If you only wanted the one in the back to get the event, then for all the controls in front of the first one, you have to set IsHitTestVisible = False for the one behind to get the event - but this isn't what you want.
If you want them all to get the event think of the entire UI as a tree of elements. All of these controls you're talking about are siblings. When something is clicked, the parent is the first to get notified, and if it doesn't handle the click, it gets passed down to the visible child element of that parent at that mouse position, and so on until it's handled. Your only way to stop the child that gets clicked from handling the mouse click is to have the common parent of all the siblings handle the event first.
You will then have to do something clever in the parent's handler to invoke the click event of all child elements that can be found beneath the mouse - the problem is that whereas the framework used to do the hard work of determining which control was under the mouse, you will will now have to do that hard work.
No chance. Even if you mark MouseClick as unhandled it will route to parent element (Canvas) not sibling. The only way is hit-testing. When the user click on Acontrol you should hit-test to determine whether another control is under it.
You must use hit-test with callbacks. This one allows you to skip Acontrol in order to find Bcontrol. If you find it, you can treat Bcontrol as clicked.

How to stop event propagation when dragging and dropping overlapping UI objects in WPF

I have a use case for dragging an item onto an area which could have multiple UI layers.
Use Case 1: Drag and drop a node from a treeview onto a workspace area(drop target). A chart is created in that workspace area.
Use Case 2: Drag and drop a node from a treeview onto a chart, that sits above the original workspace area. Extra data is added to the chart.
The problem with my implementation is that when I conduct Use Case 2, the drop event for the workspace area (Grid) AND chart are called. Not just the one that is visible.
How are others implementing this scenario?
To stop routed event propagation set e.Handled = true in drop event handler.
I would not call your case as "multi-layer". Instead, it is a normal hierarchy of visual objects. For "multi-layer" I mean, for instance, a drop on several sibling canvas.
Anyway, assuming that there is an unique point where the drop-action in managed, I would listen to the drop event (which is bubbling). In the event handler, there is the "OriginalSource" parameter that indicates the very first visual object involved. At this point, you may scan the visual tree upward (VisualTreeHelper), searching for a "chart" element: if you find it, the drop was on a chart, otherwise was on the empty workspace area.
Hope it helps.
Cheers

Categories

Resources