如何为新面板重用现有布局代码 Class?
How to Reuse Existing Layouting Code for new Panel Class?
tl;dr: 我想为自定义 WPF 面板 class 重用 pre-defined WPF panel 的现有布局逻辑。这个问题包含四种不同的尝试来解决这个问题,每一种都有不同的缺点,因此有不同的失败点。另外,可以在下面找到一个小的测试用例。
问题是:我该如何正确实现这个目标
- 定义我的自定义面板,同时
- 内部重用另一个面板的布局逻辑,没有
- 运行进入我尝试解决的问题中描述这个?
我正在尝试编写自定义 WPF panel。对于此面板 class,我想坚持推荐的开发实践并保持干净的 API 和内部实现。具体来说,这意味着:
- 我想避免复制和粘贴代码;如果多个代码部分具有相同的功能,则该代码应只存在一次并重复使用。
- 我想应用适当的封装,只让外部用户访问可以安全使用的成员(不破坏任何内部逻辑,或者不泄露任何内部 implementation-specific 信息)。
至于目前,我将继续使用现有的布局,我想 re-use 另一个面板的布局代码(而不是像建议的那样重新编写布局代码,例如 here). For the sake of an example, I will explain based on DockPanel
,尽管我想知道如何基于任何类型的 Panel
.
通常执行此操作
为了重用布局逻辑,我打算在我的面板中添加一个 DockPanel
作为可视化 child,然后它将保存和布局我的逻辑 children面板。
关于如何解决这个问题,我尝试了三种不同的想法,并且在评论中提出了另一种想法,但到目前为止,每种想法都在不同的地方失败了:
1) 在自定义面板的控件模板中引入内部布局面板
这似乎是最优雅的解决方案 - 这样,自定义面板的控制面板可以具有自定义面板的 ItemsControl
whose ItemsPanel
property is uses a DockPanel
, and whose ItemsSource
property is bound to the Children
property。
遗憾的是,Panel
does not inherit from Control
and hence does not have a Template
property 也不支持控件模板。
另一方面,Children
property 是由 Panel
引入的,因此在 Control
中不存在,我觉得打破预期可能被认为是 hacky继承层次结构并创建一个实际上是 Control
但不是 Panel
.
的面板
2) 提供我面板的 children 列表,它只是内部面板 children 列表的包装器
这样的 class 看起来如下图所示。我已经 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
,所以我确定没有调用其他可覆盖成员。)
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
布局按预期重用。唯一的问题是绑定无法按名称在面板中找到控件(使用 ElementName
property)。
我已经尝试从 LogicalChildren
property 返回内部 children,但这并没有改变任何东西:
protected override System.Collections.IEnumerator LogicalChildren {
get {
return innerPanel.Children.GetEnumerator();
}
}
在每个 child 的 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
中,但需要检索正确的 NameScope
实例。另外,我不确定 child 的名称更改时的行为是否与其他面板中的相同。
相反,设置内部面板的 NameScope
似乎是可行的方法。我尝试使用直接绑定(在 TestPanel1
构造函数中):
BindingOperations.SetBinding(innerPanel,
NameScope.NameScopeProperty,
new Binding("(NameScope.NameScope)") {
Source = this
});
不幸的是,这只是将内部面板的 NameScope
设置为 null
。据我所知,通过 parent window 的 Snoop, the actual NameScope
instance is only stored in the NameScope
attached property 或由控件模板(或可能由其他关键节点)定义的封闭可视化树的根?),无论什么类型。当然,一个控件实例在其生命周期中可能会在控件树的不同位置添加和删除,因此相关的 NameScope
可能会不时更改。这再次需要绑定。
这是我再次陷入困境的地方,因为不幸的是,无法定义 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。
除非 this other question about how to react to updates in the surrounding visual tree 产生有用的响应,是否有更好的方法来检索 and/or 绑定到当前与任何给定框架元素相关的 NameScope
?
3) 使用内部面板,其 children 列表与外部面板的实例完全相同
不是将 child 列表保留在内部面板中并将调用转发到外部面板的 child 列表,而是 kind-of 相反。在这里,只使用了外部面板的 child 列表,而内部面板从不创建自己的列表,而只是使用相同的实例:
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)
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
。
此外,控件也不可聚焦,例如按 Ta。我不知道如何解决这个问题。
此外,我对这种方式有点谨慎,因为我不确定内部面板和 children 的原始列表之间有哪些内部链接,我通过替换 [=204] 的列表来破坏=]ren 自定义 object.
4) 直接从面板继承class
用户Clemens suggests to directly have my class inherit from DockPanel
。但是,这不是一个好主意的原因有两个:
- 我的面板的当前版本将依赖于
DockPanel
的布局逻辑。但是,有可能在未来的某个时候,这将不再足够,并且确实需要有人在我的面板中编写自定义布局逻辑。在这种情况下,用自定义布局代码替换内部 DockPanel
是微不足道的,但是从我的面板的继承层次结构中删除 DockPanel
将意味着重大更改。
- 如果我的面板继承自
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
属性,那么任何自动生成的布局都可以通过自定义面板的 public 界面完全销毁。
作为观察所描述问题的测试用例,在 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方法的实现:
编辑:
如果你想要 "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);
}
现在绑定 {Binding ElementName=innerPanelButtonName, Path=Content}
将在运行时工作。
问题是可靠地找到根 UI 元素以获得 NameScope
(这里:Application.Current.MainWindow
- 在设计时不起作用)
OP 编辑:这个答案让我走上了正确的轨道,因为它提到了 NameScope
class。
我的最终解决方案基于 TestPanel1
并使用 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 的自定义实现,而不是 null
:
RegisterName
和 UnregisterName
将它们的调用转发给找到的 INameScope
对象的相应方法,否则抛出异常。
FindName
将其调用转发给找到的 INameScope
对象的 FindName
,否则(如果未找到此类对象)returns null
.
该 INameScope
实现的一个实例被设置为内部面板的 NameScope
。
我不确定你是否可以完全隐藏面板的内部结构——据我所知,WPF 在构建 visual/logical 树时没有使用 "backdoor" 访问权限,所以一旦你隐藏来自用户的东西,你也从 WPF 中隐藏它。我要做的是使结构 "read-only" (通过保持结构可访问,您无需担心绑定机制)。为此,我建议从 UIElementCollection
派生并覆盖所有用于更改集合状态的方法,并将其用作面板的子集合。至于 "tricking" XAML 将子项直接添加到内部面板中,您可以简单地将 ContentPropertyAttribute
与 属性 一起使用,以暴露内部面板的子项集合。这是此类面板的一个工作示例(至少对于您的测试用例而言):
[ContentProperty("Items")]
public class CustomPanel : Panel
{
public CustomPanel()
{
//the Children property seems to be lazy-loaded so we need to
//call the getter to invoke CreateUIElementCollection
Children.ToString();
}
private readonly Panel InnerPanel = new DockPanel();
public UIElementCollection Items { get { return InnerPanel.Children; } }
protected override Size ArrangeOverride(Size finalSize)
{
InnerPanel.Arrange(new Rect(new Point(0, 0), finalSize));
return finalSize;
}
protected override UIElementCollection CreateUIElementCollection(FrameworkElement logicalParent)
{
return new ChildCollection(this);
}
protected override Size MeasureOverride(Size availableSize)
{
InnerPanel.Measure(availableSize);
return InnerPanel.DesiredSize;
}
private sealed class ChildCollection : UIElementCollection
{
public ChildCollection(CustomPanel owner)
: base(owner, owner)
{
//call the base method (not the override) to add the inner panel
base.Add(owner.InnerPanel);
}
public override int Add(UIElement element) { throw new NotSupportedException(); }
public override void Clear() { throw new NotSupportedException(); }
public override void Insert(int index, UIElement element) { throw new NotSupportedException(); }
public override void Remove(UIElement element) { throw new NotSupportedException(); }
public override void RemoveAt(int index) { throw new NotSupportedException(); }
public override void RemoveRange(int index, int count) { throw new NotSupportedException(); }
public override UIElement this[int index]
{
get { return base[index]; }
set { throw new NotSupportedException(); }
}
}
}
或者,您可以跳过 ContentPropertyAttribute
并使用 public new UIElementCollection Children { get { return InnerPanel.Children; } }
公开内部面板的子集合 - 这也可行,因为 ContentPropertyAttribute("Children")
是从 Panel
继承的。
备注
为了防止使用隐式样式篡改内部面板,您可能需要使用 new DockPanel { Style = null }
.
初始化内部面板
tl;dr: 我想为自定义 WPF 面板 class 重用 pre-defined WPF panel 的现有布局逻辑。这个问题包含四种不同的尝试来解决这个问题,每一种都有不同的缺点,因此有不同的失败点。另外,可以在下面找到一个小的测试用例。
问题是:我该如何正确实现这个目标
- 定义我的自定义面板,同时
- 内部重用另一个面板的布局逻辑,没有
- 运行进入我尝试解决的问题中描述这个?
我正在尝试编写自定义 WPF panel。对于此面板 class,我想坚持推荐的开发实践并保持干净的 API 和内部实现。具体来说,这意味着:
- 我想避免复制和粘贴代码;如果多个代码部分具有相同的功能,则该代码应只存在一次并重复使用。
- 我想应用适当的封装,只让外部用户访问可以安全使用的成员(不破坏任何内部逻辑,或者不泄露任何内部 implementation-specific 信息)。
至于目前,我将继续使用现有的布局,我想 re-use 另一个面板的布局代码(而不是像建议的那样重新编写布局代码,例如 here). For the sake of an example, I will explain based on DockPanel
,尽管我想知道如何基于任何类型的 Panel
.
为了重用布局逻辑,我打算在我的面板中添加一个 DockPanel
作为可视化 child,然后它将保存和布局我的逻辑 children面板。
关于如何解决这个问题,我尝试了三种不同的想法,并且在评论中提出了另一种想法,但到目前为止,每种想法都在不同的地方失败了:
1) 在自定义面板的控件模板中引入内部布局面板
这似乎是最优雅的解决方案 - 这样,自定义面板的控制面板可以具有自定义面板的 ItemsControl
whose ItemsPanel
property is uses a DockPanel
, and whose ItemsSource
property is bound to the Children
property。
遗憾的是,Panel
does not inherit from Control
and hence does not have a Template
property 也不支持控件模板。
另一方面,Children
property 是由 Panel
引入的,因此在 Control
中不存在,我觉得打破预期可能被认为是 hacky继承层次结构并创建一个实际上是 Control
但不是 Panel
.
2) 提供我面板的 children 列表,它只是内部面板 children 列表的包装器
这样的 class 看起来如下图所示。我已经 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
,所以我确定没有调用其他可覆盖成员。)
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
布局按预期重用。唯一的问题是绑定无法按名称在面板中找到控件(使用 ElementName
property)。
我已经尝试从 LogicalChildren
property 返回内部 children,但这并没有改变任何东西:
protected override System.Collections.IEnumerator LogicalChildren {
get {
return innerPanel.Children.GetEnumerator();
}
}
在每个 child 的 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
中,但需要检索正确的 NameScope
实例。另外,我不确定 child 的名称更改时的行为是否与其他面板中的相同。
相反,设置内部面板的 NameScope
似乎是可行的方法。我尝试使用直接绑定(在 TestPanel1
构造函数中):
BindingOperations.SetBinding(innerPanel,
NameScope.NameScopeProperty,
new Binding("(NameScope.NameScope)") {
Source = this
});
不幸的是,这只是将内部面板的 NameScope
设置为 null
。据我所知,通过 parent window 的 Snoop, the actual NameScope
instance is only stored in the NameScope
attached property 或由控件模板(或可能由其他关键节点)定义的封闭可视化树的根?),无论什么类型。当然,一个控件实例在其生命周期中可能会在控件树的不同位置添加和删除,因此相关的 NameScope
可能会不时更改。这再次需要绑定。
这是我再次陷入困境的地方,因为不幸的是,无法定义 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。
除非 this other question about how to react to updates in the surrounding visual tree 产生有用的响应,是否有更好的方法来检索 and/or 绑定到当前与任何给定框架元素相关的 NameScope
?
3) 使用内部面板,其 children 列表与外部面板的实例完全相同
不是将 child 列表保留在内部面板中并将调用转发到外部面板的 child 列表,而是 kind-of 相反。在这里,只使用了外部面板的 child 列表,而内部面板从不创建自己的列表,而只是使用相同的实例:
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)
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
。
此外,控件也不可聚焦,例如按 Ta。我不知道如何解决这个问题。
此外,我对这种方式有点谨慎,因为我不确定内部面板和 children 的原始列表之间有哪些内部链接,我通过替换 [=204] 的列表来破坏=]ren 自定义 object.
4) 直接从面板继承class
用户Clemens suggests to directly have my class inherit from DockPanel
。但是,这不是一个好主意的原因有两个:
- 我的面板的当前版本将依赖于
DockPanel
的布局逻辑。但是,有可能在未来的某个时候,这将不再足够,并且确实需要有人在我的面板中编写自定义布局逻辑。在这种情况下,用自定义布局代码替换内部DockPanel
是微不足道的,但是从我的面板的继承层次结构中删除DockPanel
将意味着重大更改。 - 如果我的面板继承自
DockPanel
, users of the panel might be able to sabotage its layouting code by messing around with properties exposed byDockPanel
, in particularLastChildFill
. And while it is just that property, I would like to use an approach that works with allPanel
subtypes. For instance, a custom panel derived fromGrid
would expose theColumnDefinitions
andRowDefinitions
属性,那么任何自动生成的布局都可以通过自定义面板的 public 界面完全销毁。
作为观察所描述问题的测试用例,在 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方法的实现:
编辑:
如果你想要 "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);
}
现在绑定 {Binding ElementName=innerPanelButtonName, Path=Content}
将在运行时工作。
问题是可靠地找到根 UI 元素以获得 NameScope
(这里:Application.Current.MainWindow
- 在设计时不起作用)
OP 编辑:这个答案让我走上了正确的轨道,因为它提到了 NameScope
class。
我的最终解决方案基于 TestPanel1
并使用 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 的自定义实现,而不是 null
:
RegisterName
和UnregisterName
将它们的调用转发给找到的INameScope
对象的相应方法,否则抛出异常。FindName
将其调用转发给找到的INameScope
对象的FindName
,否则(如果未找到此类对象)returnsnull
.
该 INameScope
实现的一个实例被设置为内部面板的 NameScope
。
我不确定你是否可以完全隐藏面板的内部结构——据我所知,WPF 在构建 visual/logical 树时没有使用 "backdoor" 访问权限,所以一旦你隐藏来自用户的东西,你也从 WPF 中隐藏它。我要做的是使结构 "read-only" (通过保持结构可访问,您无需担心绑定机制)。为此,我建议从 UIElementCollection
派生并覆盖所有用于更改集合状态的方法,并将其用作面板的子集合。至于 "tricking" XAML 将子项直接添加到内部面板中,您可以简单地将 ContentPropertyAttribute
与 属性 一起使用,以暴露内部面板的子项集合。这是此类面板的一个工作示例(至少对于您的测试用例而言):
[ContentProperty("Items")]
public class CustomPanel : Panel
{
public CustomPanel()
{
//the Children property seems to be lazy-loaded so we need to
//call the getter to invoke CreateUIElementCollection
Children.ToString();
}
private readonly Panel InnerPanel = new DockPanel();
public UIElementCollection Items { get { return InnerPanel.Children; } }
protected override Size ArrangeOverride(Size finalSize)
{
InnerPanel.Arrange(new Rect(new Point(0, 0), finalSize));
return finalSize;
}
protected override UIElementCollection CreateUIElementCollection(FrameworkElement logicalParent)
{
return new ChildCollection(this);
}
protected override Size MeasureOverride(Size availableSize)
{
InnerPanel.Measure(availableSize);
return InnerPanel.DesiredSize;
}
private sealed class ChildCollection : UIElementCollection
{
public ChildCollection(CustomPanel owner)
: base(owner, owner)
{
//call the base method (not the override) to add the inner panel
base.Add(owner.InnerPanel);
}
public override int Add(UIElement element) { throw new NotSupportedException(); }
public override void Clear() { throw new NotSupportedException(); }
public override void Insert(int index, UIElement element) { throw new NotSupportedException(); }
public override void Remove(UIElement element) { throw new NotSupportedException(); }
public override void RemoveAt(int index) { throw new NotSupportedException(); }
public override void RemoveRange(int index, int count) { throw new NotSupportedException(); }
public override UIElement this[int index]
{
get { return base[index]; }
set { throw new NotSupportedException(); }
}
}
}
或者,您可以跳过 ContentPropertyAttribute
并使用 public new UIElementCollection Children { get { return InnerPanel.Children; } }
公开内部面板的子集合 - 这也可行,因为 ContentPropertyAttribute("Children")
是从 Panel
继承的。
备注
为了防止使用隐式样式篡改内部面板,您可能需要使用 new DockPanel { Style = null }
.