0

Instead of posting my code here, I will just outline the basic tree in my program. I have a TabControl with an Item in it and a few "treed" elements as follows:

TabControl
->TabItem
  ->UserControl(Grid with Columns and Rows)
    ->ScrollViewer(Within one of the Grid.Columns/Grid.Rows, also part of the UserControl)
      ->Grid myGrid(added in code during runtime)
        ->...a couple more things

Now in code I add an event to myGrid and I noticed the error when I tried to manually scroll the ScrollViewer, only when ctrl is pressed.

myGrid.PreviewMouseWheel += HandlePreviewMouseWheel;

private void HandlePreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
    e.Handled = true;

    bool isCtrl = Keyboard.IsKeyDown(Key.LeftCtrl);
    if (isCtrl)
    {
        if (e.Delta > 0)
            ((sender as Grid).Parent as ScrollViewer).LineUp();
        else
            ((sender as Grid).Parent as ScrollViewer).LineDown();
     }
}

That threw an exception on the type casting: An exception of type 'System.NullReferenceException' occurred in dfviewer.exe but was not handled in user code. Additional information: Object reference not set to an instance of an object.

I noticed that sender is actually not myGrid but instead labled as {System.Windows.Controls.TabControl Items.Count:1}. But how is that possible if I explicitly added the event to myGrid? Is it possible that I am missing something else here?

I have tried using MouseWheel instead of PreviewMouseWheel and it gave me the same issue and both e.Source and e.OriginalSource are not myGrid but instead "my UserControl" and "some Child of myGrid", respecitvely.

The only other post that I found a bit similar to this is here but that is unfortunately not exactly my issue here.

Hopefully, somebody can help me, because I am really lost...

Community
  • 1
  • 1
philkark
  • 2,417
  • 7
  • 38
  • 59

2 Answers2

1

You have used a Tunneling event, so your discovered behaviour is expected. From the Routed Events Overview page on MSDN:

Tunneling: Initially, event handlers at the element tree root are invoked. The routed event then travels a route through successive child elements along the route, towards the node element that is the routed event source (the element that raised the routed event). Tunneling routed events are often used or handled as part of the compositing for a control, such that events from composite parts can be deliberately suppressed or replaced by events that are specific to the complete control. Input events provided in WPF often come implemented as a tunneling/bubbling pair. Tunneling events are also sometimes referred to as Preview events, because of a naming convention that is used for the pairs.

Intead, you can use the related Bubbling event, MouseWheel. Again, from the linked page:

Bubbling: Event handlers on the event source are invoked. The routed event then routes to successive parent elements until reaching the element tree root. Most routed events use the bubbling routing strategy. Bubbling routed events are generally used to report input or state changes from distinct controls or other UI elements.

Of course, as that is still a RoutedEvent, it can still be invoked by parent elements. Your best option is to simply check if sender is a ScrollViewer before casting it, or often you'll find your required UI element in the e.OriginalSource property of the MouseWheelEventArgs object instead or as well as in the sender parameter.

Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • Thanks for your answer. I should have mentioned that I have actually tried both bubbling and tunneling and both give the same sender and the same issue. Also, both e.Source and e.OriginalSource don't help here, as e.Source is actually my UserControl and e.OriginalSource is some child element of myGrid. – philkark Dec 23 '14 at 11:33
  • Then just check the `Type` before you try to cast. – Sheridan Dec 23 '14 at 11:36
  • Wait, are you suggesting that the event is actually fired multiple times from different Controls during the tunneling? Sorry, if I have missed that /= – philkark Dec 23 '14 at 11:39
  • Thank you that solved everything! I see that it indeed fired the event on quite a few controls and not only on the one that I have assigned the event to. Still, I find this a bit odd... – philkark Dec 23 '14 at 11:42
  • *I find this a bit odd*... it's actually very useful. I suggest that you read the linked page so that you can understand why it works that way. – Sheridan Dec 23 '14 at 11:58
1

EDIT: to OP's request, changed it to handle multiple items.

When you are registering the event:

ScrollViewer gridParent = myGrid.Parent as ScrollViewer;
myGrid.PreviewMouseWheel += (sender, args) => Grid_PreviewMouseWheel(sv, args);

And in the handler, you already get the scroll viewer:

void Grid_PreviewMouseWheel(ScrollViewer scrollViewer, MouseWheelEventArgs e)
{
    e.Handled=true;
    bool isCtrl = Keyboard.IsKeyDown(Key.LeftCtrl);
    if (isCtrl)
    {
        if (e.Delta > 0)
            scrollViewer.LineUp();
        else
            scrollViewer.LineDown();
     }
}

WPF routed events are behaving somewhat differently, try reading http://msdn.microsoft.com/en-us/library/ms742806%28v=vs.110%29.aspx#routing_strategies for details.

A preview event will go through the visual tree and you are catching anything that gets to myGrid, not just events that were created there.

what you can do si save a reference to myGrid:

ScrollViewer gridParent;

...
gridParent = myGrid.Parent as ScrollViewer;
myGrid.PreviewMouseWheel += HandlePreviewMouseWheel;

and in the handler code:

bool isCtrl = Keyboard.IsKeyDown(Key.LeftCtrl);
if (isCtrl)
{
    if (e.Delta > 0)
        gridParent.LineUp();
    else
        gridParent.LineDown();
 }
ShayD
  • 920
  • 8
  • 18
  • Thank you for your suggestion. That would indeed be possible, if there was only one of theose ScrollViewers and Grids. Unfortunately, the user has the choice of adding them during runtime and may easily end up with up to 50. Also, the event should obviously only trigger on the one ScrollViewer (or rather grid) the event is fired from, since I only want to scroll that one. – philkark Dec 23 '14 at 11:38