重新想象 Windows 8 Store Apps (17) - 控件基础: Measure, Arrange, GeneralTransform, VisualTree

简介: 原文:重新想象 Windows 8 Store Apps (17) - 控件基础: Measure, Arrange, GeneralTransform, VisualTree[源码下载] 重新想象 Windows 8 Store Apps (17) - 控件基础: Measure, Arran...
原文: 重新想象 Windows 8 Store Apps (17) - 控件基础: Measure, Arrange, GeneralTransform, VisualTree

[源码下载]


重新想象 Windows 8 Store Apps (17) - 控件基础: Measure, Arrange, GeneralTransform, VisualTree



作者:webabcd


介绍
重新想象 Windows 8 Store Apps 之 控件基础

  • Measure() 和 Arrange() - xaml 的 layout 系统
  • GeneralTransform - 通过 UIElement.TransformToVisual() 获取元素的位置信息
  • VisualTree - 可视树



示例
1、演示 xaml 的 layout 系统
Controls/Basic/MeasureArrange.xaml

<Page
    x:Class="XamlDemo.Controls.Basic.MeasureArrange"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:XamlDemo.Controls.Basic"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="Transparent">

        <local:MyStackPanel Margin="120 0 0 0" HorizontalAlignment="Left" VerticalAlignment="Top" Width="200" Height="200">

            <TextBlock Text="我是文本" Width="100" Height="100" />
            <Button Content="我是按钮" Width="150" Height="150" />

        </local:MyStackPanel>

    </Grid>
</Page>

Controls/Basic/MeasureArrange.xaml.cs

/*
 * 演示 Layout 系统
 * 
 * win8 xaml 的 layout 就是一个递归系统,本 demo 就递归的一个过程做说明(步骤顺序参见代码注释中的序号)
 */

using System;
using System.Diagnostics;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace XamlDemo.Controls.Basic
{
    public sealed partial class MeasureArrange : Page
    {
        public MeasureArrange()
        {
            this.InitializeComponent();
        }
    }

    public class MyStackPanel : StackPanel
    {
        // 1、首先爸爸知道自己能够提供的尺寸 availableSize,然后告诉儿子们
        protected override Size MeasureOverride(Size availableSize)
        {
            // 2、儿子们收到 availableSize 后,又结合了自身的实际情况,然后告诉爸爸儿子们所期望的尺寸 desiredSize
            Size desiredSize = base.MeasureOverride(availableSize);
            Debug.WriteLine("availableSize: " + availableSize.ToString());
            Debug.WriteLine("desiredSize: " + desiredSize.ToString());
            return desiredSize;


            // 以下是自定义的 Measure 逻辑,供参考
            /*
            Size childrenSize = new Size(0, 0);
            foreach (UIElement child in this.Children)
            {
                child.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
                childrenSize.Width += child.DesiredSize.Width;
                childrenSize.Height += child.DesiredSize.Height;
            }
            return childrenSize;
            */
        }

        // 3、爸爸收到儿子们的反馈后,告诉儿子们自己最终提供的尺寸 finalSize
        protected override Size ArrangeOverride(Size finalSize)
        {
            // 4、儿子们根据 finalSize 安排各自的位置,然后爸爸的呈现尺寸也就确定了 renderSize
            Size renderSize = base.ArrangeOverride(finalSize);
            Debug.WriteLine("finalSize: " + finalSize.ToString());
            Debug.WriteLine("renderSize: " + renderSize.ToString());
            return renderSize;


            // 以下是自定义的 Arrange 逻辑,供参考
            /*
            Point childPos = new Point(0, 0);
            foreach (UIElement child in this.Children)
            {
                child.Arrange(new Rect(childPos, new Size(child.DesiredSize.Width, child.DesiredSize.Height)));
                childPos.Y += child.RenderSize.Height;
            }
            return finalSize;
            */
        }
    }
}

/*
 * 输出结果:
 * availableSize: 200,200
 * desiredSize: 150,250
 * finalSize: 200,250
 * renderSize: 200,250
*/


/*
 * 注:
 * UIElement
 *     调用 Measure() 方法后会更新 DesiredSize 属性
 *     调用 Arrange() 方法后会更新 RenderSize 属性
 *     UpdateLayout() - 强制 layout 递归更新
 * 
 * FrameworkElement - 继承自 UIElement
 *     MeasureOverride() - 重写 Measure()
 *     ArrangeOverride() - 重写 Arrange()
 *     ActualWidth 和 ActualHeight 来自 RenderSize,每次 UpdateLayout() 后都会被更新
 */


2、演示如何获取UI元素的位置信息
Controls/Basic/GeneralTransformDemo.xaml

<Page
    x:Class="XamlDemo.Controls.Basic.GeneralTransformDemo"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:XamlDemo.Controls.Basic"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="Transparent">
        <StackPanel Margin="120 0 0 0">
            
            <Grid HorizontalAlignment="Left" VerticalAlignment="Top">
                <Rectangle Name="rectangle1" Width="300" Height="200" Fill="Red" />
                <Rectangle Name="rectangle2" Width="150" Height="100" Fill="Green" />
            </Grid>

            <TextBlock Name="lblMsg" FontSize="14.667" Margin="0 10 0 0" />
            
        </StackPanel>
    </Grid>
</Page>

Controls/Basic/GeneralTransformDemo.xaml.cs

/*
 * 演示 GeneralTransform 的应用,可以通过 UIElement.TransformToVisual() 获取
 */

using System;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

namespace XamlDemo.Controls.Basic
{
    public sealed partial class GeneralTransformDemo : Page
    {
        public GeneralTransformDemo()
        {
            this.InitializeComponent();

            this.Loaded += TransformToVisual_Loaded;
        }

        void TransformToVisual_Loaded(object sender, RoutedEventArgs e)
        {
            lblMsg.Text = "";
            Demo1();
            lblMsg.Text += Environment.NewLine;
            Demo2();
        }

        // 演示如何获取 UIElement 相对于屏幕原点所占用的矩形区域
        private void Demo1()
        {
            GeneralTransform generalTransform = rectangle1.TransformToVisual(null); // 获取 rectangle1 相对于屏幕的 GeneralTransform
            Point point = generalTransform.TransformPoint(new Point(0, 0)); // rectangle1 的原点(左上角顶点)相对于屏幕 0,0 点的位置
            Rect rect = new Rect(point, new Size(rectangle1.ActualWidth, rectangle1.ActualHeight));

            lblMsg.Text += "红色矩形相对于屏幕原点的位置:" + rect.ToString();
        }

        // 演示如何获取 UIElement 相对于另一个 UIElement 原点所占用的矩形区域
        private void Demo2()
        {
            GeneralTransform generalTransform = rectangle1.TransformToVisual(rectangle2); // 获取 rectangle1 相对于 rectangle2 的 GeneralTransform
            Point point = generalTransform.TransformPoint(new Point(0, 0)); // rectangle1 的原点(左上角顶点)相对于 rectangle2 的原点(左上角顶点)的位置
            Rect rect = new Rect(point, new Size(rectangle1.ActualWidth, rectangle1.ActualHeight));

            lblMsg.Text += "红色矩形相对于绿色矩形左上角顶点的位置:" + rect.ToString();
        }
    }
}


3、演示 VisualTreeHelper 的应用
Controls/Basic/VisualTree.xaml

<Page
    x:Class="XamlDemo.Controls.Basic.VisualTree"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:XamlDemo.Controls.Basic"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="Transparent">
        <StackPanel Margin="120 0 0 0">
            
            <Grid Name="container" HorizontalAlignment="Left" VerticalAlignment="Top" Tapped="container_Tapped_1">
                <Rectangle Name="rectangle" Width="300" Height="200" Fill="Red" />
                <Border Name="border" Width="200" Height="120" Background="Green" />
                <ScrollViewer Name="scrollViewer" Width="150" Height="150" Background="Blue" />
            </Grid>

            <TextBlock Name="lblMsg" FontSize="14.667" Margin="0 10 0 0" />
            
        </StackPanel>
    </Grid>
</Page>

Controls/Basic/VisualTree.xaml.cs

/*
 * 演示 VisualTreeHelper 的应用
 * 
 * VisualTreeHelper - 访问可视树的帮助类
 *     GetChildrenCount(DependencyObject reference) - 获取指定的元素内的子元素的数量
 *     DependencyObject GetChild(DependencyObject reference, int childIndex) - 获取指定的元素内的,指定索引位置的子元素
 *     GetParent(DependencyObject reference) - 获取指定的元素的父元素
 *     FindElementsInHostCoordinates(Point intersectingPoint, UIElement subtree, bool includeAllElements) - 查找某一点内的全部元素(包括控件模板内的元素)
 *         intersectingPoint - 指定的点的坐标
 *         subtree - 在此元素内进行查找,包括此元素
 *         includeAllElements
 *             true - 查找全部元素,包括 IsHitTestVisible 为 true 的和 IsHitTestVisible 为 false 的
 *             false - 仅查找 IsHitTestVisible 为 true 的元素
 *     FindElementsInHostCoordinates(Rect intersectingRect, UIElement subtree, bool includeAllElements) - 查找某一矩形区域内的全部元素(包括控件模板内的元素)
 *         intersectingRect - 指定的矩形区域
 *         subtree - 在此元素内进行查找,包括此元素
 *         includeAllElements
 *             true - 查找全部元素,包括 IsHitTestVisible 为 true 的和 IsHitTestVisible 为 false 的
 *             false - 仅查找 IsHitTestVisible 为 true 的元素
 */

using System;
using System.Collections.Generic;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Media;

namespace XamlDemo.Controls.Basic
{
    public sealed partial class VisualTree : Page
    {
        public VisualTree()
        {
            this.InitializeComponent();

            this.Loaded += VisualTree_Loaded;
        }

        void VisualTree_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e)
        {
            // 获取 container 中包含的元素
            lblMsg.Text = "container 中包含的元素有:";
            int numVisuals = VisualTreeHelper.GetChildrenCount(container);
            for (int i = 0; i < numVisuals; i++)
            {
                DependencyObject element = VisualTreeHelper.GetChild(container, i);
                lblMsg.Text += Environment.NewLine;
                lblMsg.Text += element.GetType().ToString();
            }


            lblMsg.Text += Environment.NewLine;
            lblMsg.Text += Environment.NewLine;


            // 在 scrollViewer 控件自身的模板中查找类型为 ScrollBar 的名为 VerticalScrollBar 的控件
            lblMsg.Text += "查找 scrollViewer 中的名为“VerticalScrollBar”的 ScrollBar 控件:";
            lblMsg.Text += Environment.NewLine;
            ScrollBar scrollBar = GetVisualChild<ScrollBar>(scrollViewer, "VerticalScrollBar");
            if (scrollBar != null)
                lblMsg.Text += "找到了";
            else
                lblMsg.Text += "未找到";
        }

        private void container_Tapped_1(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
        {
            // 获取鼠标单击的位置,container 范围内所包含的全部元素(包括控件模板内的元素)
            lblMsg.Text = "鼠标单击的位置,container 内,包含的元素有:";
            IEnumerable<UIElement> elementsPoint = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(null), container, true);
            var elementsPointEnumerator = elementsPoint.GetEnumerator();
            while (elementsPointEnumerator.MoveNext())
            {
                lblMsg.Text += Environment.NewLine;
                lblMsg.Text += elementsPointEnumerator.Current.GetType().ToString();
            }


            lblMsg.Text += Environment.NewLine;
            lblMsg.Text += Environment.NewLine;


            // 获取以鼠标单击的位置为顶点,100*100 大小的矩形内,container 范围内所包含的全部元素(包括控件模板内的元素)
            lblMsg.Text += "以鼠标单击的位置为顶点,100*100 大小的矩形范围内,container 内,包含的元素有:";
            IEnumerable<UIElement> elementsRect = VisualTreeHelper.FindElementsInHostCoordinates(new Rect(e.GetPosition(null), new Size(100, 100)), container, true);
            var elementsRectEnumerator = elementsRect.GetEnumerator();
            while (elementsRectEnumerator.MoveNext())
            {
                lblMsg.Text += Environment.NewLine;
                lblMsg.Text += elementsRectEnumerator.Current.GetType().ToString();
            }
        }

        /// <summary>
        /// 获取指定元素内部的指定名称的 FrameworkElement
        /// </summary>
        private T GetVisualChild<T>(DependencyObject parent, string name) 
            where T : FrameworkElement
        {
            // T 是引用类型则为 null,T 是值类型则为 0
            T child = default(T);

            int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < numVisuals; i++)
            {
                DependencyObject obj = VisualTreeHelper.GetChild(parent, i);
                child = obj as T;

                if (child == null || child.Name != name)
                    child = GetVisualChild<T>(obj, name);
                if (child != null)
                    break;
            }
            return child;
        }
    }
}



OK
[源码下载]

目录
相关文章
|
Linux C++ Windows
【Azure 应用服务】Azure App Service(Windows)环境中如何让.NET应用调用SAP NetWeaver RFC函数
【Azure 应用服务】Azure App Service(Windows)环境中如何让.NET应用调用SAP NetWeaver RFC函数
138 0
【Azure 应用服务】Azure App Service(Windows)环境中如何让.NET应用调用SAP NetWeaver RFC函数
|
10月前
|
C# Windows
【Azure App Service】在App Service for Windows上验证能占用的内存最大值
根据以上测验,当使用App Service内存没有达到预期的值,且应用异常日志出现OutOfMemory时,就需要检查Platform的设置是否位64bit。
160 11
|
Java 应用服务中间件 开发工具
[App Service for Windows]通过 KUDU 查看 Tomcat 配置信息
[App Service for Windows]通过 KUDU 查看 Tomcat 配置信息
133 2
|
Java 应用服务中间件 Windows
【App Service for Windows】为 App Service 配置自定义 Tomcat 环境
【App Service for Windows】为 App Service 配置自定义 Tomcat 环境
114 2
|
PHP Windows
【Azure App Service for Windows】 PHP应用出现500 : The page cannot be displayed because an internal server error has occurred. 错误
【Azure App Service for Windows】 PHP应用出现500 : The page cannot be displayed because an internal server error has occurred. 错误
209 1
|
PHP 开发工具 git
【Azure 应用服务】在 App Service for Windows 中自定义 PHP 版本的方法
【Azure 应用服务】在 App Service for Windows 中自定义 PHP 版本的方法
118 1
|
网络安全 API 数据安全/隐私保护
【Azure App Service】.NET代码实验App Service应用中获取TLS/SSL 证书 (App Service Windows)
【Azure App Service】.NET代码实验App Service应用中获取TLS/SSL 证书 (App Service Windows)
114 0
|
Shell PHP Windows
【Azure App Service】Web Job 报错 UNC paths are not supported. Defaulting to Windows directory.
【Azure App Service】Web Job 报错 UNC paths are not supported. Defaulting to Windows directory.
109 0
|
存储 Linux Windows
【应用服务 App Service】App Service For Windows 如何挂载Storage Account File Share 示例
【应用服务 App Service】App Service For Windows 如何挂载Storage Account File Share 示例
113 0
|
应用服务中间件 nginx Windows
【Azure 应用服务】在App Service for Windows中实现反向代理
【Azure 应用服务】在App Service for Windows中实现反向代理
131 0

热门文章

最新文章