2

In the making of a customcontrol, I'm trying to get the mouse position on a Canvas which is a container inside my custom control. Problem is, when I put my customcontrol in a testing app, just a grid in a window, what I get is always the mouse position on the window itself but not the mouse position on my customcontrol.

public class HueWheel : Control
{
    static HueWheel()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(HueWheel), new FrameworkPropertyMetadata(typeof(HueWheel)));
    }
    private bool _isPressed = false;
    //private Canvas _templateCanvas = null;
    private Canvas _PART_FirstCanvas;
    private Canvas _PART_SecondCanvas;
    private Slider _PART_Slider;

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        _PART_FirstCanvas = (Canvas)GetTemplateChild("PART_FirstCanvas");
        _PART_SecondCanvas = (Canvas)GetTemplateChild("PART_SecondCanvas");
        _PART_Slider = (Slider)GetTemplateChild("PART_Slider");
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        if (_isPressed)
        {
            const double RADIUS = 150;
            Point newPos = e.GetPosition(_PART_SecondCanvas);
            double angle = MyHelper.GetAngleR(newPos, RADIUS);
            _PART_Slider.Value = (_PART_Slider.Maximum - _PART_Slider.Minimum) * angle  / (2 * Math.PI);
        }
    }

    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
    {
        _isPressed = true;
    }

    protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
    {
        _isPressed = false;
    }
}

This is the xaml contained in Generic.xaml

<Style TargetType="{x:Type local:HueWheel}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:HueWheel}">
                <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
                    <Slider x:Name="PART_Slider">
                        <Slider.Template>
                            <ControlTemplate>
                                <Canvas x:Name="PART_FirstCanvas" Width="300" Height="300">
                                        <Image Stretch="Fill" Source="Assets/HueCircle.PNG" Focusable="False" Height="300" Width="300" RenderTransformOrigin="0.5,0.5">
                                            <Image.RenderTransform>
                                                <TransformGroup>
                                                    <ScaleTransform/>
                                                    <SkewTransform/>
                                                    <RotateTransform Angle="270"/>
                                                    <TranslateTransform/>
                                                </TransformGroup>
                                            </Image.RenderTransform>
                                        </Image>
                                        <Ellipse Fill="Transparent" Width="300" Height="300" Canvas.Left="0" Canvas.Top="0"/>
                                        <Canvas x:Name="PART_SecondCanvas">
                                            <Line Stroke="Transparent" StrokeThickness="5" X1="150" Y1="150" X2="150" Y2="0"/>
                                            <Ellipse Fill="Black" Width="20" Height="20" Canvas.Left="140" Canvas.Top="30"/>
                                            <Canvas.RenderTransform>
                                                <RotateTransform CenterX="150" CenterY="150">
                                                    <RotateTransform.Angle>
                                                        <MultiBinding Converter="{StaticResource ValueAngleConverter}">
                                                            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Value"/>
                                                            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Minimum"/>
                                                            <Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Maximum"/>
                                                        </MultiBinding>
                                                    </RotateTransform.Angle>
                                                </RotateTransform>
                                            </Canvas.RenderTransform>
                                        </Canvas>
                                    </Canvas>
                            </ControlTemplate>
                        </Slider.Template>
                    </Slider>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Also, the mouse position is output only if mouse is on canvas, so it's kind of half working here... little confuse.

These are the two function I use for angle calculation :

public static double GetAngle(double value, double maximum, double minimum)
{
    double current = (value / (maximum - minimum)) * 360;
    if (current == 360)
        current = 359.999;

    return current;
}


public static double GetAngleR(Point pos, double radius)
{
    Point center = new Point(radius, radius);
    double xDiff = center.X - pos.X;
    double yDiff = center.Y - pos.Y;
    double r = Math.Sqrt(xDiff * xDiff + yDiff * yDiff);

    double angle = Math.Acos((center.Y - pos.Y) / r);

    if (pos.X < radius)
        angle = 2 * Math.PI - angle;

    if (Double.IsNaN(angle))
        return 0.0;
    else
        return angle;
}

Any hint ?

Thank you

lecloneur
  • 424
  • 5
  • 20
  • Possible duplicate of [Get Absolute Position of element within the window in wpf](http://stackoverflow.com/questions/386731/get-absolute-position-of-element-within-the-window-in-wpf) – stijn Aug 05 '16 at 07:40
  • 1
    that's not a duplicate as I'm trying to get the position within the customcontrol, not within the window. – lecloneur Aug 05 '16 at 10:27

2 Answers2

1

In your code

Point ControlPos = e.GetPosition (this);
Point WindowPos = e.GetPosition( Application.Current.MainWindow );

Will give you - coordinates in the Control and Window coordinates.

Leonid Malyshev
  • 475
  • 1
  • 6
  • 14
  • but I want to get the mouse position in my customcontrol code so it is reusable. – lecloneur Aug 05 '16 at 07:52
  • What is "me" in your suggestion ? – lecloneur Aug 05 '16 at 08:46
  • And you also can get relative position directly calling `e.GetPosition(Application.Current.MainWindow);` – Leonid Malyshev Aug 05 '16 at 08:51
  • Point relativeLocation = _PART_SecondCanvas.TranslatePoint(newPos, Application.Current.MainWindow); This gave me an error, "Object reference not set to an instance of an object." Strange because _PART_SecondCanvas is declared above already... – lecloneur Aug 05 '16 at 08:55
  • so apparently _PART_SecondCanvas, is always null... is it because of how OnApplyTemplate() is invoked or something ? can't find how to fix that but your solution might be the one I think – lecloneur Aug 05 '16 at 12:05
  • Get position directly relative to the main window as in my previous comment. And don't bother about `_PART_SecondCanvas` – Leonid Malyshev Aug 05 '16 at 12:09
  • sorry I'm beginner. Trying what your wrote : Point newPos = e.GetPosition(Application.Current.MainWindow); give me the exact same result. Position on screen... – lecloneur Aug 05 '16 at 12:13
  • Point newPos = e.GetPosition(_PART_SecondCanvas); Point relativeLocation = this.TranslatePoint(newPos, Application.Current.MainWindow); it gives me the same, this part of the customcontrol is inside a container (in Generic.xaml) could it be the problem ? – lecloneur Aug 05 '16 at 13:05
  • If `_PART_SecondCanvas` is null, you need to fix that. Is `_PART_FirstCanvas ` also null? – Sphinxxx Aug 05 '16 at 13:23
1

_PART_SecondCanvas and _PART_FirstCanvas are null because they belong to the child Slider's control template, and not the "main" HueWheel template. Therefore, you can't reach them with GetTemplateChild() from HueWheel.

In OnApplyTemplate(), you need to find the slider first, then apply its template, and finally call FindName() on that template to find the canvas elements:

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    //_PART_FirstCanvas = (Canvas)GetTemplateChild("PART_FirstCanvas");
    //_PART_SecondCanvas = (Canvas)GetTemplateChild("PART_SecondCanvas");
    _PART_Slider = (Slider)GetTemplateChild("PART_Slider");

    _PART_Slider.ApplyTemplate();

    var sliderTemplate = _PART_Slider.Template;
    _PART_FirstCanvas  = (Canvas)sliderTemplate.FindName("PART_FirstCanvas",  _PART_Slider);
    _PART_SecondCanvas = (Canvas)sliderTemplate.FindName("PART_SecondCanvas", _PART_Slider);
}

Source: How do I get the Children of a ContentPresenter?

Once you actually find _PART_SecondCanvas and _PART_FirstCanvas, your OnMouseMove() function should do what you expect.

Community
  • 1
  • 1
Sphinxxx
  • 12,484
  • 4
  • 54
  • 84