tags:

views:

222

answers:

1

Hello all.

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!

A: 

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.

John Bowen
Evil Closet Monkey
Another point of interest to this -- the ResizeGrip is defined in a style (currently) located in a ResourceDictionary. I've run into many problems trying to define events in that situation.
Evil Closet Monkey
Updated with more details.
John Bowen
Thank you for the pointers John.I was able to get everything set up as you suggest and based on what I had previously coded all the bindings still worked. No need for the DependencyProperty... at least yet. Surprised the heck out of me too!Unfortunately this didn't fix the problem I was having, as the ResizeGrip still loses the mouse capture when the pointer "falls" off it (on a slower computer). But this has helped narrow down the potential cause of that!
Evil Closet Monkey