如何重用现有的布点code新的面板类? [英] How to Reuse Existing Layouting Code for new Panel Class?

查看:296
本文介绍了如何重用现有的布点code新的面板类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

TL;博士:我想重新使用pre定义的WPF面板为自定义WPF面板类。这个问题包含四个不同的尝试,以解决这个问题,每一个不同的缺点,因此不同的故障点。此外,一个小的测试情况下,可以进一步降低被发现。

tl;dr: I want to reuse the existing layouting logic of a pre-defined WPF panel for a custom WPF panel class. This question contains four different attempts to solve this, each with different downsides and thus a different point of failure. Also, a small test case can be found further down.

现在的问题是:如何正确地实现这一目标。

The question is: How do I properly achieve this goal of

  • 定义我的自定义面板,而
  • 在内部重用另一个面板的布点逻辑,而不
  • 在运行到我的企图描述的问题解决呢?

我试图写一个自定义WPF <一href="https://msdn.microsoft.com/en-us/library/system.windows.controls.panel%28v=vs.110%29.aspx">panel.对于这个面板类,我想坚持推荐的开发实践,保持一个干净的API和内部实现。具体而言,这意味着:

I am trying to write a custom WPF panel. For this panel class, I would like to stick to recommended development practices and maintain a clean API and internal implementation. Concretely, that means:

  • 我想,以避免复印件及code粘贴;如果code几个部分具有相同的功能,code应当只存在一次,并且可以重复使用。
  • 我想申请适当的封装,并让外部用户只能访问这些成员,可以安​​全使用(不破坏任何的内在逻辑,还是不放弃任何内部执行相关的信息)。

至于目前,我将密切与现有布局坚持,我想重新使用另一个小组的布点code(而不是C再次写布点$ C $,作为建议如< A HREF =htt​​p://stackoverflow.com/a/3223327/1430156>这里)。为了一个例子中,我将解释根据<一href="https://msdn.microsoft.com/en-us/library/system.windows.controls.dockpanel(v=vs.110).aspx"><$c$c>DockPanel,但我想知道如何做到这一点一般的基础上,任何一种面板

As for the time being, I am going to closely stick with an existing layout, I would like to re-use another panel's layouting code (rather than writing the layouting code again, as suggested e.g. here). For the sake of an example, I will explain based on DockPanel, though I would like to know how to do this generally, based on any kind of Panel.

要重复使用的布点逻辑,我打算增加一个 DockPanel中作为一种视觉的孩子在我的面板,然后将持有和布局我的面板的逻辑孩子。

To reuse the layouting logic, I am intending to add a DockPanel as a visual child in my panel, which will then hold and layout the logical children of my panel.

我试图就如何解决这个三个不同的概念,和另外一个建议的意见,但他们每个人至今无法在不同的点:

I have tried three different ideas on how to solve this, and another one was suggested in a comment, but each of them so far fails at a different point:

1)引进内布局面板的自定义面板

这似乎是最优雅的解决方案 - 这样一来,控制面板的自定义面板可以配备一个<一个href="https://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol%28v=vs.110%29.aspx"><$c$c>ItemsControl他的<一个href="https://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.itemspanel(v=vs.110).aspx"><$c$c>ItemsPanel物业是使用了 DockPanel中,且其<一href="https://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.itemssource(v=vs.110).aspx"><$c$c>ItemsSource物业绑定到<一个href="https://msdn.microsoft.com/en-us/library/system.windows.controls.panel.children%28v=vs.110%29.aspx"><$c$c>Children自定义面板的属性。

This seems like the most elegant solution - this way, a control panel for the custom panel could feature an ItemsControl whose ItemsPanel property is uses a DockPanel, and whose ItemsSource property is bound to the Children property of the custom panel.

不幸的是,<一个href="https://msdn.microsoft.com/en-us/library/system.windows.controls.panel%28v=vs.110%29.aspx"><$c$c>Panel不从<一个继承href="https://msdn.microsoft.com/en-us/library/system.windows.controls.control%28v=vs.110%29.aspx"><$c$c>Control并因此不具有<一个href="https://msdn.microsoft.com/en-us/library/system.windows.controls.control.template%28v=vs.110%29.aspx"><$c$c>Template物业,也不是功能支持控件模板。

Unfortunately, Panel does not inherit from Control and hence does not have a Template property, nor feature support for control templates.

在另一方面,在<一href="https://msdn.microsoft.com/en-us/library/system.windows.controls.panel.children%28v=vs.110%29.aspx"><$c$c>Children物业由面板介绍,因此并不present在控制,我觉得它可以考虑哈克打破预期的继承层次,并创建一个面板,实际上是一个控制,而不是面板

On the other hand, the Children property is introduced by Panel, and hence not present in Control, and I feel it could be considered hacky to break out of the intended inheritance hierarchy and create a panel that is actually a Control, but not a Panel.

2)提供我的面板的孩子列表仅仅是围绕着内板的儿童名单

这样的类看起来描述如下。我有子类<一href="https://msdn.microsoft.com/en-us/library/system.windows.controls.uielementcollection%28v=vs.110%29.aspx"><$c$c>UIElementCollection在我的面板类,然后从的<一个一个重写版本回到住处href="https://msdn.microsoft.com/en-us/library/system.windows.controls.panel.createuielementcollection(v=vs.110).aspx"><$c$c>CreateUIElementCollection法。 (我只是复制了实际上这里调用的方法,我已经实现了其他人抛出一个<一个href="https://msdn.microsoft.com/en-us/library/system.notimplementedexception%28v=vs.110%29.aspx"><$c$c>NotImplementedException,所以我敢肯定,没有其他overrideable成员被调用。)

Such a class looks as depicted below. I have subclassed UIElementCollection in my panel class and returned it from an overridden version of the CreateUIElementCollection method. (I have only copied the methods that are actually invoked here; I have implemented the others to throw a NotImplementedException, so I am certain that no other overrideable members were invoked.)

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;
        }
    }
}

该作品的几乎的正确;在 DockPanel中布局重用预期。唯一的问题是,绑定没有找到控制在面板按名称(用<一href="https://msdn.microsoft.com/en-us/library/system.windows.data.binding.elementname%28v=vs.110%29.aspx"><$c$c>ElementName物业)。

This works almost correctly; the DockPanel layout is reused as expected. The only issue is that bindings do not find controls in the panel by name (with the ElementName property).

我试图返回从<一个内部的孩子href="https://msdn.microsoft.com/en-us/library/system.windows.controls.panel.logicalchildren%28v=vs.110%29.aspx"><$c$c>LogicalChildren物业,但这并没有改变什么:

I have tried returned the inner children from the LogicalChildren property, but this did not change anything:

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

在一个回答通过用户的阿里的<一个href="https://msdn.microsoft.com/en-us/library/system.windows.namescope%28v=vs.100%29.aspx"><$c$c>NameScope类被指出了在这至关重要的作用:子控件的名字没有得到相关的名称范围出于某种原因注册。这的也许的已部分修复通过调用<一href="https://msdn.microsoft.com/en-us/library/system.windows.namescope.registername(v=vs.100).aspx"><$c$c>RegisterName对于每个孩子,而是一个需要获取正确的名称范围实例。另外,我不知道是否该行为时,例如,一个孩子改变名称将是相同的其他面板。

In an answer by user Arie, the NameScope class was pointed out to have a crucial role in this: The names of the child controls do not get registered in the relevant NameScope for some reason. This might be partially fixed by invoking RegisterName for each child, but one would need to retrieve the correct NameScope instance. Also, I am not sure whether the behaviour when, for instance, the name of a child changes would be the same as in other panels.

相反,设定名称范围的内板似乎是要走的路。我想这有一个简单的绑定(在 TestPanel1 构造函数):

Instead, setting the NameScope of the inner panel seems to be the way to go. I tried this with a straightforward binding (in the TestPanel1 constructor):

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

不幸的是,这只是设置了名称范围的内板以。据我可以找出由史努比的手段,实际名称范围实例只存储在 名称范围无论是父窗口,或者由控制模板中定义封闭的视觉树的根的附加属性(或可能通过一些其他关键节点?),不管是什么类型。当然,一个控件实例可以添加和删除在其一生中的一个控件树的位置不同,所以相关的名称范围可能从随时更改。这又要求具有约束力。

Unfortunately, this just sets the NameScope of the inner panel to null. As far as I could find out by means of Snoop, the actual NameScope instance is only stored in the NameScope attached property of either the parent window, or the root of the enclosing visual tree defined by a control template (or possibly by some other key node?), no matter what type. Of course, a control instance may be added and removed at different positions in a control tree during its lifetime, so the relevant NameScope might change from time to time. This, again, calls for a binding.

这是我在哪里再坚持,因为不幸的是,我们不能定义一个<一个href="https://msdn.microsoft.com/en-us/library/system.windows.data.relativesource%28v=vs.100%29.aspx"><$c$c>RelativeSource对于基于任意条件,例如*第一个遇到的节点具有非约束 - 分配到的 名称范围附加属性

This is where I am stuck again, because unfortunately, one cannot define a RelativeSource for the binding based on an arbitrary condition such as *the first encountered node that has a non-null value assigned to the NameScope attached property.

除非有关如何在周围的可视化树应对这一更新等问题产生一个有用的答复,是否有更好的方法来检索和/或绑定到名称范围目前相关的任何给定的框架元素?

Unless this other question about how to react to updates in the surrounding visual tree yields a useful response, is there a better way to retrieve and/or bind to the NameScope currently relevant for any given framework element?

3)使用内板,其子女名单根本就是同一个实例作为外板的

而不是保持孩子列表中的内板和转发调用外板的子级列表,这个作品倒过来的慈祥。这里,仅外面板的子级列表时,而内板从未创建其自己的一个,而是简单地使用相同的实例:

Rather than keeping the child list in the inner panel and forwarding calls to the outer panel's child list, this works kind-of the other way round. Here, only the outer panel's child list is used, while the inner panel never creates one of its own, but simply uses the same instance:

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;
        }
    }
}

下面,布点和作品名称绑定控制。但是,控制是不可点击。

Here, layouting and binding to controls by name works. However, the controls are not clickable.

我怀疑我必须以某种方式转移呼叫到<一个href="https://msdn.microsoft.com/en-us/library/ms598914%28v=vs.110%29.aspx"><$c$c>HitTestCore(GeometryHitTestParameters)和<一href="https://msdn.microsoft.com/en-us/library/ms598915%28v=vs.110%29.aspx"><$c$c>HitTestCore(PointHitTestParameters)到内板。但是,在所述内板,我只能访问<一href="https://msdn.microsoft.com/en-us/library/system.windows.uielement.inputhittest%28v=vs.110%29.aspx"><$c$c>InputHitTest,所以我既不知道如何安全地处理原始<一href="https://msdn.microsoft.com/en-us/library/system.windows.media.hittestresult%28v=vs.110%29.aspx"><$c$c>HitTestResult例如,而不会丢失或忽略的任何信息,原来的实施将有尊重,也没有如何处理<一href="https://msdn.microsoft.com/en-us/library/system.windows.media.geometryhittestparameters%28v=vs.110%29.aspx"><$c$c>GeometryHitTestParameters,为 InputHitTest 只接受一个简单的<一个href="https://msdn.microsoft.com/en-us/library/system.windows.point(v=vs.110).aspx"><$c$c>Point.

I suspect I have to somehow forward calls to HitTestCore(GeometryHitTestParameters) and to HitTestCore(PointHitTestParameters) to the inner panel. However, in the inner panel, I can only access InputHitTest, so I am neither sure how to safely process the raw HitTestResult instance without losing or ignoring any of the information that the original implementation would have respected, nor how to process the GeometryHitTestParameters, as InputHitTest only accepts a simple Point.

此外,控制也不可作为焦点,例如:由pressing <大骨节病>标签。我不知道如何解决这个问题。

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

此外,我稍微警惕会这样,因为我不知道什么是内板和孩子,我通过更换孩子的列表,自定义对象打破,原有的列表之间的内在联系。

Besides, I am slightly wary of going this way, as I am not sure what internal links between the inner panel and the original list of children I am breaking by replacing that list of children with a custom object.

4)直接从面板类继承

用户克莱门斯建议直接从<一有我的类继承href="https://msdn.microsoft.com/en-us/library/system.windows.controls.dockpanel%28v=vs.110%29.aspx"><$c$c>DockPanel.但是,有两个原因,为什么这不是一个好主意:

User Clemens suggests to directly have my class inherit from DockPanel. However, there are two reasons why that is not a good idea:

  • 在我的面板的当前版本将依托 DockPanel中的布点逻辑。但是,它有可能在未来的某个时刻,这将是不够的更多的,有人确实会对编写自定义我的面板上布点的逻辑。在这种情况下,更换里面的 DockPanel中自定义布点code是微不足道的,但是从继承删除 DockPanel中我的面板的层次结构将意味着一个重大更改。
  • 如果我的面板继承自<一个href="https://msdn.microsoft.com/en-us/library/system.windows.controls.dockpanel%28v=vs.110%29.aspx"><$c$c>DockPanel,面板的用户可能会通过与 DockPanel中公开的属性,特别是<乱搞一个破坏其布点code href="https://msdn.microsoft.com/en-us/library/system.windows.controls.dockpanel.lastchildfill%28v=vs.110%29.aspx"><$c$c>LastChildFill.虽然这仅仅是该财产,我想用一个能与所有的工作方法<一href="https://msdn.microsoft.com/en-us/library/system.windows.controls.panel%28v=vs.110%29.aspx"><$c$c>Panel亚型。例如,定制面板从<派生href="https://msdn.microsoft.com/en-us/library/system.windows.controls.grid%28v=vs.110%29.aspx"><$c$c>Grid会暴露<一href="https://msdn.microsoft.com/en-us/library/system.windows.controls.grid.columndefinitions%28v=vs.110%29.aspx"><$c$c>ColumnDefinitions和<一href="https://msdn.microsoft.com/en-us/library/system.windows.controls.grid.rowdefinitions%28v=vs.110%29.aspx"><$c$c>RowDefinitions性能,其中任何自动生成的布局可以完全通过自定义面板的公共接口销毁。
  • The current version of my panel will rely on the layouting logic of DockPanel. However, it is possible that at some point in the future, that will not be enough any more and someone will indeed have to write custom layouting logic in my panel. In that case, replacing an inner DockPanel with custom layouting code is trivial, but removing DockPanel from the inheritance hierarchy of my panel would mean a breaking change.
  • If my panel inherits from DockPanel, users of the panel might be able to sabotage its layouting code by messing around with properties exposed by DockPanel, in particular LastChildFill. And while it is just that property, I would like to use an approach that works with all Panel subtypes. For instance, a custom panel derived from Grid would expose the ColumnDefinitions and RowDefinitions properties, by which any automatically generated layout could be completely destroyed via the public interface of the custom panel.

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

As a test case for observing the described issues, add an instance of the custom panel being tested in XAML, and within that element, add the following:

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

该文本块应保留文本框的,它应该表现出任何当前写在文本框中。

The text block should be left of the text box, and it should show whatever is currently written in the text box.

我期望的文本框中可以点击,并输出视图不显示任何有约束力的错误(因此,结合应该工作,以及)。

I would expect the text box to be clickable, and the output view not to display any binding errors (so, the binding should work, as well).

因此​​,我的问题是:

Thus, my question is:

  • 我可以尝试任何一种固定导致一个完全正确的解决方案吗?或者是有是preferrable什么,我试图做我所期待的完全相反?

推荐答案

如果你与你的第二个方法唯一的问题(提供我的面板的孩子列表仅仅是围绕着内板的儿童名单的包装)是缺乏按名称绑定到内板的控制的能力,那么soluton将是:

If your only problem with your second approach (Provide a children list of my panel that is merely a wrapper around the children list of the inner panel) is the lack of ability to bind to the inner panel's controls by name, then the soluton would be:

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

和然后,示例性结合:

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

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

the implementation of FindChild method: http://stackoverflow.com/a/1759923/891715

编辑:

如果你想在正常<一href="https://msdn.microsoft.com/en-us/library/system.windows.data.binding.elementname(v=vs.110).aspx"相对=nofollow>通过绑定的ElementName 的工作,你就必须<一个href="https://msdn.microsoft.com/en-us/library/system.windows.frameworkelement.registername(v=vs.100).aspx"相对=nofollow>寄存器的控制属于innerPanel在适当的名称范围

If you want the "usual" binding by ElementName to work, you'll have to register the names of controls that are children of the innerPanel in the appropriate NameScope:

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

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

现在绑定 {绑定的ElementName = innerPanelButtonName,路径=内容} 将在运行时工作。

Now the binding {Binding ElementName=innerPanelButtonName, Path=Content} will work at runtime.

这样做的问题是找到可靠的根UI元素,以获得名称范围(此处为: Application.Current.MainWindow - 不能在设计时使用)

The problem with this is reliably finding the root UI element to get the NameScope (here: Application.Current.MainWindow - won't work in design time)

编辑由OP:这个答案给我带来了正确的轨道上,因为它提到的 名称范围

EDIT by OP: This answer brought me on the right track, as it mentioned the NameScope class.

我的最终解决方案是基于 TestPanel1 并使用自定义实现的 INameScope 接口。它的每一个方法走到逻辑树,起于外板,找到最近的父元素,其的 名称范围属性是不是

My final solution is based on TestPanel1 and uses a custom implementation of the INameScope interface. Each of its methods walks up the logical tree, starting at the outer panel, to find the nearest parent element whose NameScope property is not null:

  • RegisterName UnregisterName 了自己调用的是找到相应​​的方法 INameScope 对象,否则抛出异常。
  • FindName 转发其调用到 FindName 找到 INameScope 对象,否则(如果没有这样的对象找到)返回
  • RegisterName and UnregisterName forward their invocation to the respective methods of that found INameScope object and otherwise throw an exception.
  • FindName forwards its invocation to FindName of the found INameScope object and otherwise (if no such object was found) returns null.

这方面的一个例子 INameScope 的实施设置为的 名称范围 的内板的。

An instance of that INameScope implementation is set as the NameScope of the inner panel.

这篇关于如何重用现有的布点code新的面板类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆