Question

I'm having some trouble with the EventToCommand not behaving as I would expect with CaptureMouse.

I have a ResizeGrip that I've defined several EventToCommand's on:

<ResizeGrip Name="ResizeGrip" HorizontalAlignment="Right" VerticalAlignment="Bottom" Cursor="SizeNWSE">
<i:Interaction.Triggers>
  <i:EventTrigger EventName="MouseLeftButtonDown">
   <cmd:EventToCommand Command="{Binding ResizeStartCommand}" PassEventArgsToCommand="True" />
  </i:EventTrigger>
  <i:EventTrigger EventName="MouseLeftButtonUp">
   <cmd:EventToCommand Command="{Binding ResizeStopCommand}" PassEventArgsToCommand="True" />
  </i:EventTrigger>
  <i:EventTrigger EventName="MouseMove">
   <cmd:EventToCommand Command="{Binding ResizeCommand}" PassEventArgsToCommand="True" />
  </i:EventTrigger>
 </i:Interaction.Triggers>
 </ResizeGrip>

The handling functions are set in the constructor of the class:

ResizeStartCommand = new RelayCommand<MouseButtonEventArgs>(
    (e) => OnRequestResizeStart(e));
ResizeStopCommand = new RelayCommand<MouseButtonEventArgs>(
    (e) => OnRequestResizeStop(e));
ResizeCommand = new RelayCommand<MouseEventArgs>(
    (e) => OnRequestResize(e),
    param => CanResize);

And finally I do all my logic to resize:

void OnRequestResizeStart(MouseButtonEventArgs e)
{
    bool r = Mouse.Capture((UIElement)e.Source);
    Console.WriteLine("mouse down: " + r.ToString());
}
void OnRequestResizeStop(MouseButtonEventArgs e)
{
    ((UIElement)e.Source).ReleaseMouseCapture();
    _canResize = false;
}
void OnRequestResize(MouseEventArgs e)
{
    Console.WriteLine("mouse move");
}
bool CanResize
{ get { return _canResize; } }

The OnRequestResizeStart & OnRequestResizeStop commands are working fine, and the OnRequestResize works... but only when I am actually over the ResizeGrip. It does not appear that the CaptureMouse is not actually sending all the mouse events to the ResizeGrip.

Is this a limitation of the EventToCommand, or does something special need to occur?

Thanks for any help!

Was it helpful?

Solution

Why are you using commands for this instead of standard event handlers? This is clearly UI logic that belongs in code-behind, not a ViewModel.

****UPDATE**

In the case that you're using this in a ControlTemplate you can treat the Control's code as your code-behind. To do the connections of the events you can use the TemplatePart pattern and connect to the specially named element in OnApplyTemplate. To facilitate providing the Size (or whatever else your VM needs) you can add a DP to store the appropriate value and bind a VM property to that. This also allows for doing two way binding if you need to do something like set an initial size from the VM.

// this doesn't enforce the name but suggests it
[TemplatePart(Name = "PART_Resizer", Type = typeof(ResizeGrip))]
public class MyContainer : ContentControl
{
    private ResizeGrip _grip;

    public static readonly DependencyProperty ContainerDimensionsProperty = DependencyProperty.Register(
        "ContainerDimensions",
        typeof(Size),
        typeof(MyContainer),
        new UIPropertyMetadata(Size.Empty, OnContainerDimensionsChanged));

    private static void OnContainerDimensionsChanged(DependencyObject dObj, DependencyPropertyChangedEventArgs e)
    {
        MyContainer myContainer = dObj as MyContainer;
        if (myContainer != null)
        {
            Size newValue = (Size)e.NewValue;
            if (newValue != Size.Empty)
            {
                myContainer.Width = newValue.Width;
                myContainer.Height = newValue.Height;
            }
        }
    }

    public Size ContainerDimensions
    {
        get { return (Size)GetValue(ContainerDimensionsProperty); }
        set { SetValue(ContainerDimensionsProperty, value); }
    }

    static MyContainer()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MyContainer), new FrameworkPropertyMetadata(typeof(MyContainer)));
    }

    public override void OnApplyTemplate()
    {
        _grip = Template.FindName("PART_Resizer", this) as ResizeGrip;
        if (_grip != null)
        {
            _grip.MouseLeftButtonDown += Grip_MouseLeftButtonDown;
            // other handlers
        }

        SizeChanged += MyContainer_SizeChanged;
        base.OnApplyTemplate();
    }

    void MyContainer_SizeChanged(object sender, SizeChangedEventArgs e)
    {
        // update your DP
    }
    ...
}

To make use of this you need to make sure you have an element in your template with the right Type and Name to match the attribute.

<local:MyContainer ContainerDimensions="{Binding Path=SavedSize}">
    <local:MyContainer.Template>
        <ControlTemplate TargetType="{x:Type local:MyContainer}">
            <Grid>
                <Border Background="{TemplateBinding Background}">
                    <ContentPresenter/>
                </Border>
                <ResizeGrip x:Name="PART_Resizer" HorizontalAlignment="Right" VerticalAlignment="Bottom"
                            Width="20" Height="20"/>
            </Grid>
        </ControlTemplate>
    </local:MyContainer.Template>
</local:MyContainer>

You can now put this template anywhere since it's not declaring any event handlers and so is independent of a code-behind file. You now have all of your UI logic encapsulated in a UI specific class but can still access data you need in your VM by binding it.

If you think about it this is the way you normally interact with built-in controls. If you use an Expander you wouldn't want to pass clicks of the ToggleButton into your VM and try to make the control expand from there, but you might want to know whether the Expander is open or closed, so there's an IsExpanded property that you can bind to and save and load as data.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top