如何为新的面板类重用现有的布局代码?

2023-12-25

tl;dr:我想重用预定义的现有布局逻辑WPF面板 https://msdn.microsoft.com/en-us/library/system.windows.controls.panel%28v=vs.110%29.aspx用于自定义 WPF 面板类。这个问题包含四种不同的尝试来解决这个问题,每种尝试都有不同的缺点,因此有不同的失败点。此外,还可以在下面找到一个小测试用例。

问题是:我该如何正确实现这个目标

  • 定义我的自定义面板
  • 内部重用另一个面板的布局逻辑,无需
  • 遇到我试图解决这个问题时描述的问题吗?

我正在尝试编写一个自定义 WPFpanel https://msdn.microsoft.com/en-us/library/system.windows.controls.panel%28v=vs.110%29.aspx。对于这个面板类,我想坚持推荐的开发实践并保持干净的 API 和内部实现。具体来说,这意味着:

  • 我想避免复制和粘贴代码;如果几部分代码具有相同的功能,则该代码应该只存在一次并可以重用。
  • 我想应用适当的封装,并让外部用户仅访问可以安全使用的成员(不破坏任何内部逻辑,或不泄露任何内部特定于实现的信息)。

就目前而言,我将严格坚持现有的布局,我想重新使用另一个面板的布局代码(而不是像建议的那样再次编写布局代码,例如here https://stackoverflow.com/a/3223327/1430156)。为了举例,我将根据DockPanel https://msdn.microsoft.com/en-us/library/system.windows.controls.dockpanel(v=vs.110).aspx,尽管我想知道基于任何类型的一般如何做到这一点Panel.

为了重用布局逻辑,我打算添加一个DockPanel作为我的面板中的视觉子项,它将保存并布局我的面板的逻辑子项。

我已经尝试了三种不同的想法来解决这个问题,评论中提出了另一种想法,但到目前为止,每种想法都在不同的点上失败了:


1)在自定义面板的控件模板中引入内部布局面板

这似乎是最优雅的解决方案 - 这样,自定义面板的控制面板可以具有ItemsControl https://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol%28v=vs.110%29.aspx whose ItemsPanel财产 https://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.itemspanel(v=vs.110).aspx是使用一个DockPanel,以及谁的ItemsSource财产 https://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.itemssource(v=vs.110).aspx绑定到Children财产 https://msdn.microsoft.com/en-us/library/system.windows.controls.panel.children%28v=vs.110%29.aspx的自定义面板。

很遗憾,Panel https://msdn.microsoft.com/en-us/library/system.windows.controls.panel%28v=vs.110%29.aspx不继承自Control https://msdn.microsoft.com/en-us/library/system.windows.controls.control%28v=vs.110%29.aspx因此没有Template财产 https://msdn.microsoft.com/en-us/library/system.windows.controls.control.template%28v=vs.110%29.aspx,也不支持控件模板。

另一方面,Children财产 https://msdn.microsoft.com/en-us/library/system.windows.controls.panel.children%28v=vs.110%29.aspx是由Panel,因此不存在于Control,我觉得打破预期的继承层次结构并创建一个实际上是一个面板的面板可能被认为是黑客行为Control,但不是一个Panel.


2)提供我的面板的子列表,它只是内部面板的子列表的包装

这样的类如下所示。我已经子类化了UIElementCollection https://msdn.microsoft.com/en-us/library/system.windows.controls.uielementcollection%28v=vs.110%29.aspx在我的面板类中,并从重写版本返回它CreateUIElementCollection method https://msdn.microsoft.com/en-us/library/system.windows.controls.panel.createuielementcollection(v=vs.110).aspx。 (我只复制了这里实际调用的方法;我已经实现了其他方法来抛出NotImplementedException https://msdn.microsoft.com/en-us/library/system.notimplementedexception%28v=vs.110%29.aspx,所以我确信没有调用其他可重写的成员。)

using System;
using System.Windows;
using System.Windows.Controls;

namespace WrappedPanelTest
{
    public class TestPanel1 : Panel
    {
        private sealed class ChildCollection : UIElementCollection
        {
            public ChildCollection(TestPanel1 owner) : base(owner, owner)
            {
                if (owner == null) {
                    throw new ArgumentNullException("owner");
                }

                this.owner = owner;
            }

            private readonly TestPanel1 owner;

            public override int Add(System.Windows.UIElement element)
            {
                return this.owner.innerPanel.Children.Add(element);
            }

            public override int Count {
                get {
                    return owner.innerPanel.Children.Count;
                }
            }

            public override System.Windows.UIElement this[int index] {
                get {
                    return owner.innerPanel.Children[index];
                }
                set {
                    throw new NotImplementedException();
                }
            }
        }

        public TestPanel1()
        {
            this.AddVisualChild(innerPanel);
        }

        private readonly DockPanel innerPanel = new DockPanel();

        protected override UIElementCollection CreateUIElementCollection(System.Windows.FrameworkElement logicalParent)
        {
            return new ChildCollection(this);
        }

        protected override int VisualChildrenCount {
            get {
                return 1;
            }
        }

        protected override System.Windows.Media.Visual GetVisualChild(int index)
        {
            if (index == 0) {
                return innerPanel;
            } else {
                throw new ArgumentOutOfRangeException();
            }
        }

        protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
        {
            innerPanel.Measure(availableSize);
            return innerPanel.DesiredSize;
        }

        protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
        {
            innerPanel.Arrange(new Rect(new Point(0, 0), finalSize));
            return finalSize;
        }
    }
}

这有效almost正确;这DockPanel布局按预期被重用。唯一的问题是绑定无法按名称在面板中找到控件(使用ElementName财产 https://msdn.microsoft.com/en-us/library/system.windows.data.binding.elementname%28v=vs.110%29.aspx).

我尝试过从LogicalChildren财产 https://msdn.microsoft.com/en-us/library/system.windows.controls.panel.logicalchildren%28v=vs.110%29.aspx,但这并没有改变任何东西:

protected override System.Collections.IEnumerator LogicalChildren {
    get {
        return innerPanel.Children.GetEnumerator();
    }
}

In an answer https://stackoverflow.com/a/31155457/1430156按用户Arie https://stackoverflow.com/users/891715/arie, the NameScope class https://msdn.microsoft.com/en-us/library/system.windows.namescope%28v=vs.100%29.aspx有人指出其中起着至关重要的作用:子控件的名称没有在相关的NameScope因为某些原因。这might通过调用部分修复RegisterName https://msdn.microsoft.com/en-us/library/system.windows.namescope.registername(v=vs.100).aspx对于每个孩子,但需要检索正确的NameScope实例。另外,我不确定孩子的名字发生变化时的行为是否与其他面板中的行为相同。

相反,设置NameScope内面板的似乎是要走的路。我尝试使用简单的绑定(在TestPanel1构造函数):

        BindingOperations.SetBinding(innerPanel,
                                     NameScope.NameScopeProperty,
                                     new Binding("(NameScope.NameScope)") {
                                        Source = this
                                     });

不幸的是,这只是设置NameScope内面板的null。据我所知,通过Snoop https://snoopwpf.codeplex.com/, 实际上NameScope实例仅存储在NameScope附属财产 https://msdn.microsoft.com/en-us/library/ms601183%28v=vs.100%29.aspx父窗口或由控件模板(或可能由某些其他关键节点?)定义的封闭可视化树的根,无论什么类型。当然,一个控件实例在其生命周期内可能会在控件树的不同位置添加和删除,因此相关的NameScope可能会不时发生变化。这再次需要绑定。

这就是我再次陷入困境的地方,因为不幸的是,人们无法定义一个RelativeSource https://msdn.microsoft.com/en-us/library/system.windows.data.relativesource%28v=vs.100%29.aspx用于基于任意条件的绑定,例如*第一个遇到的具有非null分配给的值NameScope附属财产 https://msdn.microsoft.com/en-us/library/ms601183%28v=vs.100%29.aspx.

Unless 关于如何对周围视觉树中的更新做出反应的另一个问题 https://stackoverflow.com/questions/31187522/capture-change-in-surrounding-visual-tree产生有用的响应,是否有更好的方法来检索和/或绑定到NameScope目前与任何给定的框架元素相关?


3)使用内部面板,其子列表与外部面板的子列表相同

这不是将子列表保留在内部面板中并将调用转发到外部面板的子列表,而是以相反的方式工作。这里,仅使用外部面板的子列表,而内部面板从不创建自己的子列表,而只是使用相同的实例:

using System;
using System.Windows;
using System.Windows.Controls;

namespace WrappedPanelTest
{
    public class TestPanel2 : Panel
    {
        private sealed class InnerPanel : DockPanel
        {
            public InnerPanel(TestPanel2 owner)
            {
                if (owner == null) {
                    throw new ArgumentNullException("owner");
                }

                this.owner = owner;
            }

            private readonly TestPanel2 owner;

            protected override UIElementCollection CreateUIElementCollection(FrameworkElement logicalParent)
            {
                return owner.Children;
            }
        }

        public TestPanel2()
        {
            this.innerPanel = new InnerPanel(this);
            this.AddVisualChild(innerPanel);
        }

        private readonly InnerPanel innerPanel;

        protected override int VisualChildrenCount {
            get {
                return 1;
            }
        }

        protected override System.Windows.Media.Visual GetVisualChild(int index)
        {
            if (index == 0) {
                return innerPanel;
            } else {
                throw new ArgumentOutOfRangeException();
            }
        }

        protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
        {
            innerPanel.Measure(availableSize);
            return innerPanel.DesiredSize;
        }

        protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
        {
            innerPanel.Arrange(new Rect(new Point(0, 0), finalSize));
            return finalSize;
        }
    }
}

在这里,按名称布局和绑定到控件是有效的。但是,控件不可单击。

我怀疑我必须以某种方式将电话转接到HitTestCore(GeometryHitTestParameters) https://msdn.microsoft.com/en-us/library/ms598914%28v=vs.110%29.aspx and to HitTestCore(PointHitTestParameters) https://msdn.microsoft.com/en-us/library/ms598915%28v=vs.110%29.aspx到内面板。但是,在内部面板中,我只能访问InputHitTest https://msdn.microsoft.com/en-us/library/system.windows.uielement.inputhittest%28v=vs.110%29.aspx,所以我不确定如何安全地处理原始数据HitTestResult https://msdn.microsoft.com/en-us/library/system.windows.media.hittestresult%28v=vs.110%29.aspx实例不会丢失或忽略原始实现会尊重的任何信息,也不知道如何处理GeometryHitTestParameters https://msdn.microsoft.com/en-us/library/system.windows.media.geometryhittestparameters%28v=vs.110%29.aspx, as InputHitTest只接受简单的Point https://msdn.microsoft.com/en-us/library/system.windows.point(v=vs.110).aspx.

Moreover, the controls are also not focusable, e.g. by pressing Tab. I do not know how to fix this.

此外,我对这种方式有点谨慎,因为我不确定通过用自定义对象替换该子列表来打破内部面板和原始子列表之间的内部链接。


4)直接继承面板类

User Clemens https://stackoverflow.com/users/1136211/clemens建议直接让我的类继承DockPanel https://msdn.microsoft.com/en-us/library/system.windows.controls.dockpanel%28v=vs.110%29.aspx。然而,有两个原因表明这不是一个好主意:

  • 我的面板的当前版本将依赖于布局逻辑DockPanel。然而,有可能在未来的某个时候,这还不够,有人确实必须在我的面板中编写自定义布局逻辑。在这种情况下,更换内部DockPanel使用自定义布局代码很简单,但是删除DockPanel从我的面板的继承层次结构来看,这意味着一个重大的改变。
  • 如果我的面板继承自DockPanel https://msdn.microsoft.com/en-us/library/system.windows.controls.dockpanel%28v=vs.110%29.aspx,面板的用户可能会通过乱搞暴露的属性来破坏其布局代码DockPanel, 尤其LastChildFill https://msdn.microsoft.com/en-us/library/system.windows.controls.dockpanel.lastchildfill%28v=vs.110%29.aspx。虽然这只是该属性,但我想使用一种适用于所有属性的方法Panel https://msdn.microsoft.com/en-us/library/system.windows.controls.panel%28v=vs.110%29.aspx亚型。例如,派生自的自定义面板Grid https://msdn.microsoft.com/en-us/library/system.windows.controls.grid%28v=vs.110%29.aspx会暴露ColumnDefinitions https://msdn.microsoft.com/en-us/library/system.windows.controls.grid.columndefinitions%28v=vs.110%29.aspx and RowDefinitions https://msdn.microsoft.com/en-us/library/system.windows.controls.grid.rowdefinitions%28v=vs.110%29.aspx属性,通过这些属性,任何自动生成的布局都可以通过自定义面板的公共接口被完全销毁。

作为观察所描述问题的测试用例,添加在 XAML 中测试的自定义面板的实例,并在该元素中添加以下内容:

<TextBox Name="tb1" DockPanel.Dock="Right"/>
<TextBlock Text="{Binding Text, ElementName=tb1}" DockPanel.Dock="Left"/>

文本块应位于文本框的左侧,并且应显示文本框中当前写入的内容。

我希望文本框是可单击的,并且输出视图不显示任何绑定错误(因此,绑定也应该起作用)。


因此,我的问题是:

  • 我的任何一项尝试是否可以修复以得出完全正确的解决方案?或者是否有一种完全其他的方式比我尝试做的事情更好?

如果第二种方法的唯一问题(提供我的面板的子列表,它只是内部面板的子列表的包装)是缺乏按名称绑定到内部面板的控件的能力,那么解决方案是:

    public DependencyObject this[string childName]
    {
        get
        {
            return innerPanel.FindChild<DependencyObject>(childName);
        }
    }

然后是示例绑定:

"{Binding ElementName=panelOwner, Path=[innerPanelButtonName].Content}"

FindChild方法的实现:https://stackoverflow.com/a/1759923/891715 https://stackoverflow.com/a/1759923/891715


EDIT:

如果你想要“平常”通过 ElementName 绑定 https://msdn.microsoft.com/en-us/library/system.windows.data.binding.elementname(v=vs.110).aspx为了工作,你必须注册控件名称 https://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.registername(v=vs.100).aspx它们是适当的innerPanel 的子级名称范围 https://msdn.microsoft.com/en-us/library/system.windows.namescope(v=vs.100).aspx:

var ns = NameScope.GetNameScope(Application.Current.MainWindow);

foreach (FrameworkElement child in innerPanel.Children)
{
    ns.RegisterName(child.Name, child);
}

现在绑定{Binding ElementName=innerPanelButtonName, Path=Content}将在运行时工作。

这样做的问题是可靠地找到根 UI 元素来获取NameScope (here: Application.Current.MainWindow- 在设计时不起作用)


OP 编辑​​:这个答案让我走上了正轨,因为它提到了NameScope class https://msdn.microsoft.com/en-us/library/system.windows.namescope%28v=vs.110%29.aspx.

我的最终解决方案是基于TestPanel1并使用的自定义实现INameScope界面 https://msdn.microsoft.com/en-us/library/system.windows.markup.inamescope%28v=vs.110%29.aspx。它的每个方法都会从外部面板开始沿着逻辑树向上查找,以查找其最近的父元素NameScope财产 https://msdn.microsoft.com/en-us/library/ms601183%28v=vs.110%29.aspx is not null:

  • RegisterName and UnregisterName将它们的调用转发到找到的相应方法INameScope对象,否则抛出异常。
  • FindName将其调用转发给FindName所发现的INameScope对象,否则(如果没有找到这样的对象)返回null.

一个这样的例子INameScope实施被设置为NameScope https://msdn.microsoft.com/en-us/library/ms601183%28v=vs.110%29.aspx内面板的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何为新的面板类重用现有的布局代码? 的相关文章

随机推荐

  • 对于作为 Azure 应用服务 (P3) 托管的 .NET Core 2 Web api 的基准 RPS,我的期望应该是什么?

    我正在尝试衡量在 NET Core 2 中开发的 Web API 的基线 RPS 以下是迄今为止我遵循的步骤 从 Microsoft 的 VS 模板中生成了一个新的 空的 Web API 项目 添加了一个新的控制器 它执行基本的 你好 你的
  • Automake 将两个静态库合并为一个

    我正在尝试将预构建静态库中的符号包含到我正在构建的静态库中 然后 我正在构建的静态库将在可执行文件内部使用 我遇到的问题是我的静态库中从其他静态库获取了未定义的符号 这意味着我从预构建的静态库中需要的符号没有包含在我的静态库中 使用自动工具
  • 在 LWUIT 中拨打电话

    我有一个带有电话号码的文本字段 我想要做的就是当文本字段聚焦并按下呼叫按钮时 必须暂停应用程序并且应使用以下命令拨打文本字段的电话号码平台请求 http docs oracle com javame config cldc ref impl
  • MKOverlayView 和触摸

    我的地图上有一个自定义 MKOverlayView 我想检测触摸 但是 我似乎无法让覆盖层做出响应 我希望这会是愚蠢的事情 比如忘记将 userInteractionEnabled 设置为 YES 但是可惜 没有运气 目前 我的情况如下 m
  • 在 php 中处理数学方程

    用户可以输入他们喜欢的任何数学方程 带有一个变量 x 5 1 x 2 x 3 56 13 它们以字符串形式存储在数据库中 当检索它们时 我需要用 x 代替数字并检查方程的值 我怎么能这样做呢 我正在考虑编写一个解析器来解构字符串并将它们转换
  • 在C#中打开路径中有空格的Word文档

    我有这条路 path Cash Report 30 03 2012 01 11 07 Cash Flow Report Docx 当我使用下面的代码打开文件时 它会尝试打开每个单词 所以它会尝试打开 cash doc 然后打开 Report
  • 来自 GitHub 的桌面通知

    当我选择的项目出现新推送时 如何获得桌面通知 任何 RSS 阅读器都应该这样做 转到 GitHub 上的仪表板页面 其中显示 您的新闻源 右上角是带有文本 新闻源 的橙色 rss 图标 您可以订阅该图标
  • Python 3,PIL是Pillow的别名吗?

    Windows 7 64 位上的 Python 3 5 我很困惑 如果有人安装Pillow 那么必须使用被取代 卸载的图形库的名称PIL作为它的别名 根据枕头安装说明 http pillow readthedocs io en 3 3 x
  • 从 Storyboard 中初始化的 UIViewController 调用方法 - Objective C

    我是新来的Storyboarding在 Objective C 中 我需要调用方法UIVIewController 前Storyboarding我正在初始化UIViewController in AppDelegate或者只是在那里分配指针
  • 如何将Tomcat的端口从8080改为80?

    我想执行我的网络应用程序http localhost 1 前往conftomcat安装目录下的文件夹 e g C Tomcat 6 0 conf 2 编辑以下标签server xml file
  • 是否可以仅将一个类的 MediaTypeFormatter 更改为 JSON?

    我有一个 Web api 其中全局配置配置为使用 XmlMediaTypeFormatter 我的问题是我不会使用新的控制器扩展此 Web api 而是使用 JsonMediaTypeFormatter 是否可以仅将一个 API 控制器类的
  • 如何以编程方式从共享点文档库下载文件

    在按钮单击事件或链接按钮单击时 我想从共享点文档库下载文档并将其保存到用户的本地磁盘 请帮助我 如果您有任何代码示例 请分享 输出文件的直接链接的问题是 对于某些内容类型 它可能只是在浏览器窗口中打开 如果这不是所需的结果 并且您想要强制保
  • 如何存储 Websphere MQ 消息以实现持久性?

    Websphere MQ 消息存储在数据库还是文件系统中 我可以将其配置为使用 MySQL 吗 WebSphere MQ 持久性始终是分布式平台的本地文件系统 在大型机上 可以在具有耦合设施的 Sysplex 中使用 DB2 数据库 但对于
  • Deno 顶级等待

    正在阅读homepage https deno land 新的 JS 运行时 deno 我看到了下面的代码 import serve from https deno land email protected cdn cgi l email
  • 使用 Gulp 较新版本的 Gulp Vinyl FTP 没有注意到文件中的更改,因此没有部署更新:时区问题

    解决 我构建了一个 gulp 插件来解决这个问题 https www npmjs com package gulp mtime Correction https www npmjs com package gulp mtime correc
  • 如何使用 mvcsitemapprovider 呈现特定站点地图部分

    假设我有站点地图
  • Oxite:你打算用它做什么? [关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • 在 Nginx 配置中运行 Lua?

    因此 当涉及到 Google 搜索时 可能只是我不是超级聪明或超级不幸 但我实际上找不到任何方法可以在 Nginx 配置中运行 Lua 而无需使用 LuaJIT 重新编译整个服务器 问题是 我们希望对一些变量进行微小的编辑 而不必在每次构建
  • 如何使用 pymodbus 写入 PLC 输入寄存器

    我想使用 pymodbus 写入 PLC 输入寄存器 我能够阅读它们 from pymodbus client sync import ModbusTcpClient client ModbusTcpClient 10 10 10 32 c
  • 如何为新的面板类重用现有的布局代码?

    tl dr 我想重用预定义的现有布局逻辑WPF面板 https msdn microsoft com en us library system windows controls panel 28v vs 110 29 aspx用于自定义 W