源项目地址:https://github.com/Microsoft/...
以下是把样例转换为简要说明,同时给出实际运行效果及关键代码:

CompositionTarget

clipboard.png
ps:刷新率=总计数/总时间秒数

public MainWindow()
{
    InitializeComponent();

    // Add an event handler to update canvas background color just before it is rendered.
    System.Windows.Media.CompositionTarget.Rendering += UpdateColor;
}

// Called just before frame is rendered to allow custom drawing.
protected void UpdateColor(object sender, EventArgs e)
{
    if (_frameCounter++ == 0)
    {
        // Starting timing.
        _stopwatch.Start();
    }

    // Determine frame rate in fps (frames per second).
    var frameRate = (long) (_frameCounter/_stopwatch.Elapsed.TotalSeconds);
    if (frameRate > 0)
    {
        // Update elapsed time, number of frames, and frame rate.
        myStopwatchLabel.Content = _stopwatch.Elapsed.ToString();
        myFrameCounterLabel.Content = _frameCounter.ToString(CultureInfo.InvariantCulture);
        myFrameRateLabel.Content = frameRate.ToString();
    }

    // Update the background of the canvas by converting MouseMove info to RGB info.
    var redColor = (byte) (_pt.X/3.0);
    var blueColor = (byte) (_pt.Y/2.0);
    myCanvas.Background = new SolidColorBrush(Color.FromRgb(redColor, 0x0, blueColor));
}

public void MouseMoveHandler(object sender, MouseEventArgs e)
{
    // Retreive the coordinates of the mouse button event.
    _pt = e.GetPosition((UIElement) sender);
}

DrawingVisual

clipboard.png


可视化宿主类继承:public class MyVisualHost : FrameworkElement
构建可视化集合:
// Create a collection of child visual objects.
private readonly VisualCollection _children;

public MyVisualHost()
{
    _children = new VisualCollection(this)
    {
        CreateDrawingVisualRectangle(),
        CreateDrawingVisualText(),
        CreateDrawingVisualEllipses()
    };

    // Add the event handler for MouseLeftButtonUp.
    MouseLeftButtonUp += MyVisualHost_MouseLeftButtonUp;
}

构建可视化对象方框、文本、圆方法

// Create a DrawingVisual that contains a rectangle.
private System.Windows.Media.DrawingVisual CreateDrawingVisualRectangle()
{
    System.Windows.Media.DrawingVisual drawingVisual = new System.Windows.Media.DrawingVisual();

    // Retrieve the DrawingContext in order to create new drawing content.
    DrawingContext drawingContext = drawingVisual.RenderOpen();

    // Create a rectangle and draw it in the DrawingContext.
    Rect rect = new Rect(new Point(160, 100), new Size(320, 80));
    drawingContext.DrawRectangle(Brushes.LightBlue, null, rect);

    // Persist the drawing content.
    drawingContext.Close();

    return drawingVisual;
}

// Create a DrawingVisual that contains text.
private System.Windows.Media.DrawingVisual CreateDrawingVisualText()
{
    // Create an instance of a DrawingVisual.
    System.Windows.Media.DrawingVisual drawingVisual = new System.Windows.Media.DrawingVisual();

    // Retrieve the DrawingContext from the DrawingVisual.
    DrawingContext drawingContext = drawingVisual.RenderOpen();

    // Draw a formatted text string into the DrawingContext.
    drawingContext.DrawText(
        new FormattedText("Click Me!",
            CultureInfo.GetCultureInfo("en-us"),
            FlowDirection.LeftToRight,
            new Typeface("Verdana"),
            36, Brushes.Black),
        new Point(200, 116));

    // Close the DrawingContext to persist changes to the DrawingVisual.
    drawingContext.Close();

    return drawingVisual;
}

// Create a DrawingVisual that contains an ellipse.
private System.Windows.Media.DrawingVisual CreateDrawingVisualEllipses()
{
    System.Windows.Media.DrawingVisual drawingVisual = new System.Windows.Media.DrawingVisual();
    DrawingContext drawingContext = drawingVisual.RenderOpen();

    drawingContext.DrawEllipse(Brushes.Maroon, null, new Point(430, 136), 20, 20);
    drawingContext.Close();

    return drawingVisual;
}

处理点击事件,根据可视化树命中测试来获取命中结果,在回调函数更改命中对象透明度

// Capture the mouse event and hit test the coordinate point value against
// the child visual objects.
private void MyVisualHost_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    // Retreive the coordinates of the mouse button event.
    Point pt = e.GetPosition((UIElement) sender);

    // Initiate the hit test by setting up a hit test result callback method.
    VisualTreeHelper.HitTest(this, null, MyCallback, new PointHitTestParameters(pt));
}

// If a child visual object is hit, toggle its opacity to visually indicate a hit.
public HitTestResultBehavior MyCallback(HitTestResult result)
{
    if (result.VisualHit.GetType() == typeof (System.Windows.Media.DrawingVisual))
    {
        ((System.Windows.Media.DrawingVisual) result.VisualHit).Opacity =
            ((System.Windows.Media.DrawingVisual) result.VisualHit).Opacity == 1.0 ? 0.4 : 1.0;
    }

    // Stop the hit test enumeration of objects in the visual tree.
    return HitTestResultBehavior.Stop;
}

以下必须部件:由系统自动调到,获取可视化子对象的索引属性

// Provide a required override for the VisualChildrenCount property.
protected override int VisualChildrenCount => _children.Count;

// Provide a required override for the GetVisualChild method.
protected override Visual GetVisualChild(int index)
{
    if (index < 0 || index >= _children.Count)
    {
        throw new ArgumentOutOfRangeException();
    }

    return _children[index];
}

可视化命中测试

clipboard.png

  • 一个控件圆圈,由几个不同半径圆组成,控制左击命中圆圈时改变颜色,右键移除控件
public class MyShape : ContainerVisual
{
    // Constant values from the "winuser.h" header file.
    public const int WmLbuttonup = 0x0202,
        WmRbuttonup = 0x0205;

    public static int NumberCircles = 5;
    public static double Radius = 50.0d;
    public static Random MyRandom = new Random();
    public static ArrayList HitResultsList = new ArrayList();

    internal MyShape()
    {
        // Create a random x:y coordinate for the shape.
        var left = MyRandom.Next(0, MyWindow.Width);
        var top = MyRandom.Next(0, MyWindow.Height);

        var currRadius = Radius;
        if (Radius == 0.0d)
        {
            currRadius = MyRandom.Next(30, 100);
        }

        // Draw five concentric circles for the shape.
        var r = currRadius;
        for (var i = 0; i < NumberCircles; i++)
        {
            new MyCircle(this, new Point(left, top), r);
            r = currRadius*(1.0d - ((i + 1.0d)/NumberCircles));
        }
    }

    // Respond to WM_LBUTTONUP or WM_RBUTTONUP messages by determining which visual object was clicked.
    public static void OnHitTest(Point pt, int msg)
    {
        // Clear the contents of the list used for hit test results.
        HitResultsList.Clear();

        // Determine whether to change the color of the circle or to delete the shape.
        if (msg == WmLbuttonup)
        {
            MyWindow.ChangeColor = true;
        }
        if (msg == WmRbuttonup)
        {
            MyWindow.ChangeColor = false;
        }

        // Set up a callback to receive the hit test results enumeration.
        VisualTreeHelper.HitTest(MyWindow.MyHwndSource.RootVisual,
            null,
            CircleHitTestResult,
            new PointHitTestParameters(pt));

        // Perform actions on the hit test results list.
        if (HitResultsList.Count > 0)
        {
            ProcessHitTestResultsList();
        }
    }

    // Handle the hit test results enumeration in the callback.
    internal static HitTestResultBehavior CircleHitTestResult(HitTestResult result)
    {
        // Add the hit test result to the list that will be processed after the enumeration.
        HitResultsList.Add(result.VisualHit);

        // Determine whether hit test should return only the top-most layer visual.
        if (MyWindow.TopmostLayer)
        {
            // Set the behavior to stop the enumeration of visuals.
            return HitTestResultBehavior.Stop;
        }
        // Set the behavior to continue the enumeration of visuals.
        // All visuals that intersect at the hit test coordinates are returned,
        // whether visible or not.
        return HitTestResultBehavior.Continue;
    }

    // Process the results of the hit test after the enumeration in the callback.
    // Do not add or remove objects from the visual tree until after the enumeration.
    internal static void ProcessHitTestResultsList()
    {
        foreach (MyCircle circle in HitResultsList)
        {
            // Determine whether to change the color of the ring or to delete the circle.
            if (MyWindow.ChangeColor)
            {
                // Draw a different color ring for the circle.
                circle.Render();
            }
            else
            {
                if (circle.Parent == MyWindow.MyHwndSource.RootVisual)
                {
                    // Remove the root visual by disposing of the host hwnd.
                    MyWindow.MyHwndSource.Dispose();
                    MyWindow.MyHwndSource = null;
                    return;
                }
                // Remove the shape that is the parent of the child circle.
                ((ContainerVisual) MyWindow.MyHwndSource.RootVisual).Children.Remove((Visual) circle.Parent);
            }
        }
    }

    internal class MyCircle : DrawingVisual
    {
        public Point Pt;
        private readonly double _radius;

        internal MyCircle(MyShape parent, Point pt, double radius)
        {
            Pt = pt;
            _radius = radius;
            Render();

            // Add the circle as a child to the shape parent.
            parent.Children.Add(this);
        }

        internal void Render()
        {
            // Draw a circle.
            var dc = RenderOpen();
            dc.DrawEllipse(new SolidColorBrush(MyColor.GenRandomColor()), null, Pt, _radius, _radius);
            dc.Close();
        }
    }

    private class MyColor
    {
        private static readonly Random myRandom = new Random();

        public static Color GenRandomColor() => Color.FromRgb((byte)myRandom.Next(0, 255), (byte)myRandom.Next(0, 255),
                (byte)myRandom.Next(0, 255));
    }
}
  • 中间窗口类
internal class MyWindow
{
    // Constant values from the "winuser.h" header file.
    internal const int WsChild = 0x40000000,
        WsVisible = 0x10000000;

    // Constant values from the "winuser.h" header file.
    internal const int WmLbuttonup = 0x0202,
        WmRbuttonup = 0x0205;

    public static int Width = Form.ActiveForm.ClientSize.Width;
    public static int Height = Form.ActiveForm.ClientSize.Height;
    public static HwndSource MyHwndSource;
    public static bool TopmostLayer = true;
    public static bool ChangeColor;

    public static void FillWithCircles(IntPtr parentHwnd)
    {
        // Fill the client area of the form with randomly placed circles.
        for (var i = 0; i < 200; i++)
        {
            CreateShape(parentHwnd);
        }
    }

    public static void CreateShape(IntPtr parentHwnd)
    {
        // Create an instance of the shape.
        var myShape = new MyShape();

        // Determine whether the host container window has been created.
        if (MyHwndSource == null)
        {
            // Create the host container window for the visual objects.
            CreateHostHwnd(parentHwnd);

            // Associate the shape with the host container window.
            MyHwndSource.RootVisual = myShape;
        }
        else
        {
            // Assign the shape as a child of the root visual.
            ((ContainerVisual) MyHwndSource.RootVisual).Children.Add(myShape);
        }
    }

    internal static void CreateHostHwnd(IntPtr parentHwnd)
    {
        // Set up the parameters for the host hwnd.
        var parameters = new HwndSourceParameters("Visual Hit Test", Width, Height)
        {
            WindowStyle = WsVisible | WsChild
        };
        parameters.SetPosition(0, 24);
        parameters.ParentWindow = parentHwnd;
        parameters.HwndSourceHook = ApplicationMessageFilter;

        // Create the host hwnd for the visuals.
        MyHwndSource = new HwndSource(parameters);

        // Set the hwnd background color to the form's background color.
        MyHwndSource.CompositionTarget.BackgroundColor = Brushes.OldLace.Color;
    }

    internal static IntPtr ApplicationMessageFilter(
        IntPtr hwnd, int message, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        // Handle messages passed to the visual.
        switch (message)
        {
            // Handle the left and right mouse button up messages.
            case WmLbuttonup:
            case WmRbuttonup:
                var pt = new Point
                {
                    X = (uint) lParam & 0x0000ffff,
                    Y = (uint) lParam >> 16
                };
                // LOWORD = x
                // HIWORD = y
                MyShape.OnHitTest(pt, message);
                break;
        }

        return IntPtr.Zero;
    }
}
  • Form类

PS:有个小bug,当命中一个创建的圆环控件时,右键时会移除所有的圆圈,原因为:控件的根未处理好。


李志玮
22 声望34 粉丝

求索~~