2

I have a flyout panel (as illustrated below), which if undocked should be invisible when the mouse leaves the panel area in general.

flyout panel

However, I don't want the panel to close if any of these conditions occur:

1) The user opens a ContextMenu

2) The user chooses a ComboBox item that falls below the panel (as illustrated above)

3) A confirmation dialog that comes up due to a user action (such as deleting an item in the DataGrid)

It's easy to track context menu operations (ContextMenuOpening and ContextMenuClosing events) to handle the first case, but I haven't found any good ways yet to handle the other two cases, in particular tracking dialogs being opened.

Any ideas?

My flyout panel is just a grid whose visibility and content is determined in code behind:

<Grid Name="UndockedGrid" ContextMenuOpening="Grid_ContextMenuOpening" ContextMenuClosing="Grid_ContextMenuClosing" MouseLeave="Grid_MouseLeave">
    <!-- Toolbox (undocked) -->
    <ScrollViewer Name="ToolBoxUndockedViewer">
        <StackPanel Name="ToolBoxUndockedPanel" />
    </ScrollViewer>
</Grid>
Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72

1 Answers1

0

I took a RoutedEvent approach to this problem, since my view models do not handle dialog workflows directly in this version.

To get the behavior I wanted for the data grid combo boxes, I extended DataGrid to add the routed events I wanted to track:

public class RoutableDataGrid : DataGrid
{
    public static readonly RoutedEvent ElementOpenedEvent = EventManager.RegisterRoutedEvent("ElementOpened", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(RoutableDataGrid));

    public event RoutedEventHandler ElementOpened
    {
        add { AddHandler(ElementOpenedEvent, value); }
        remove { RemoveHandler(ElementOpenedEvent, value); }
    }

    public static readonly RoutedEvent ElementClosedEvent = EventManager.RegisterRoutedEvent("ElementClosed", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(RoutableDataGrid));

    public event RoutedEventHandler ElementClosed
    {
        add { AddHandler(ElementClosedEvent, value); }
        remove { RemoveHandler(ElementClosedEvent, value); }
    }

    public void RaiseElementOpened()
    {
        RaiseEvent(new RoutedEventArgs(RoutableDataGrid.ElementOpenedEvent, this));
    }

    public void RaiseElementClosed()
    {
        RaiseEvent(new RoutedEventArgs(RoutableDataGrid.ElementClosedEvent, this));
    }
}

And the DataGridComboBoxColumn was extended to fire the new routed events:

public class BindableDataGridComboBoxColumn : DataGridComboBoxColumn
{
    protected RoutableDataGrid ParentGrid { get; set; }

    protected FrameworkElement Element { get; set; }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        Element = base.GenerateEditingElement(cell, dataItem);
        Element.MouseEnter += new System.Windows.Input.MouseEventHandler(element_MouseEnter);
        Element.MouseLeave += new System.Windows.Input.MouseEventHandler(element_MouseLeave);
        CopyItemsSource(Element);
        return Element;
    }

    void element_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
    {
        if (ParentGrid != null)
        {
            ParentGrid.RaiseElementClosed();
        }
    }

    void element_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
    {
        if (ParentGrid != null)
        {
            ParentGrid.RaiseElementOpened();
        }
    }
}

More of a hack, since MessageBox is a sealed class, I had to build a wrapper class to fire routed events using a MessageBox (and put an instance of this element in the xaml so that it's in the visual tree):

public class RoutedEventPlaceHolder : UIElement
{
    public static readonly RoutedEvent ElementOpenedEvent = EventManager.RegisterRoutedEvent("ElementOpened", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(RoutedEventPlaceHolder));

    public event RoutedEventHandler ElementOpened
    {
        add { AddHandler(ElementOpenedEvent, value); }
        remove { RemoveHandler(ElementOpenedEvent, value); }
    }

    public static readonly RoutedEvent ElementClosedEvent = EventManager.RegisterRoutedEvent("ElementClosed", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(RoutedEventPlaceHolder));

    public event RoutedEventHandler ElementClosed
    {
        add { AddHandler(ElementClosedEvent, value); }
        remove { RemoveHandler(ElementClosedEvent, value); }
    }

    public void RaiseElementOpened()
    {
        RaiseEvent(new RoutedEventArgs(RoutedEventPlaceHolder.ElementOpenedEvent, this));
    }

    public void RaiseElementClosed()
    {
        RaiseEvent(new RoutedEventArgs(RoutedEventPlaceHolder.ElementClosedEvent, this));
    }

    public MessageBoxResult ShowMessageBox(string text, string caption, MessageBoxButton button)
    {
        RaiseElementOpened();
        MessageBoxResult result = MessageBox.Show(text, caption, button);
        RaiseElementClosed();
        return result;
    }
}

Then finally I can subscribe to the new routed events in the flyout panel:

        <Grid Name="UndockedGrid" lib:RoutedEventPlaceHolder.ElementOpened="Grid_ElementOpened" lib:RoutableDataGrid.ElementOpened="Grid_ElementOpened" ContextMenuOpening="Grid_ContextMenuOpening" ContextMenuClosing="Grid_ContextMenuClosing" MouseLeave="Grid_MouseLeave">
            <!-- Toolbox (undocked) -->
            <ScrollViewer Grid.Row="0" Grid.Column="0" Name="ToolBoxUndockedViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Visibility="Collapsed" MouseLeave="ToolBoxUndockedViewer_MouseLeave">
                <StackPanel Name="ToolBoxUndockedPanel" MinWidth="200" Background="{StaticResource ControlBackgroundBrush}" />
            </ScrollViewer>
        </Grid>

This is not an ideal solution. I'll accept any other answers that are more elegant.

Dave Clemmer
  • 3,741
  • 12
  • 49
  • 72