我找不到Panel.IstItemsHost附加属性的任何好文档。我看到很多人在ItemsControl的ItemsContainer模板上设置它的例子,但在MSDN上的un-documentation并没有解释为什么或设置属性赋予什么优势。我已经构建了大量不设置此属性的容器,但尚未注意到任何不良影响。
说我有一个ItemsControl。我想使用一个自定义面板,在滚动时将项目放入和拉出;它叫做SwoopPanel。现在,我如何告诉ItemsControl使用我的SwoopPanel来包含它创建的模板?
快速的方法是在ItemsControl上设置ItemsPanel:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<lol:SwoopPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
但是,有时这对你不起作用。也许您希望自定义SwoopPanel在UI中的显示方式,解决此问题的唯一方法是更改ItemsControl的控件模板。现在,您可以将SwoopPanel直接添加到控件模板中,并使用该属性将其标记为ItemsControl将放置它创建的所有模板化项目的ItemsHost。
<Style TargetType="ItemsControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ItemsControl">
<Border CornerRadius="5">
<ScrollViewer VerticalScrollBarVisibility="Hidden">
<lol:SwoopPanel IsItemsHost="True"/>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
你必须以某种方式做到这一点吗?不是。比另一个更有优势吗?好吧,第二种方式可以让你更多地控制UI,第一种方式更容易。接受你的选择,真的。我从来没有亲自以第二种方式完成它,但我认为可能有几个地方可能有用。
见http://msdn.microsoft.com/en-us/library/system.windows.controls.panel.isitemshost(v=vs.90).aspx
本质上,这篇文章所说的是,如果你要替换ListBox的ControlTemplate并想要一个新的布局,在某些面板上设置IsItemsHost = true,例如一个StackPanel。然后,ListBox中的任何项目将自动添加为StackPanel的子项。如果ListBox的方向是水平,则ListBox将是水平的。
另一种方法是将ListBox的ItemsPanel属性设置为ItemsTemplate,并在该模板中设置StackPanel。在这种情况下,ListBox项目将添加到StackPanel子项中,就像在第一种情况下一样。但是,您不需要设置IsItemsHost = true,它将完全没有效果。您可以通过设置ItemsPanel属性来完成此操作。
虽然上述所有答案在技术上都是正确的,但我觉得它们没有说明IsItemsPanel
如何与ControlTemplate
以及ItemsPresenter
的存在(或缺席)以及它使用的相应的ItemsPanel
属性相关联。这个答案将试图揭示这些事情,并希望澄清何时应该或不应该使用每一个。
ItemsControl
只是一个显示项目集合的控件。它通过首先为每个项目生成一个ItemContainer
来实现这一点,然后将这些容器插入(或从中删除)特定的“主机”面板,最后,该面板将容器放出以供显示。
用于托管容器的特定面板是ItemControl
层次结构中的第一个面板,其IsItemsHost
属性设置为“True”。有两种方法可以指定哪个面板:
ItemsPresenter
插入ControlTemplate
,然后设置ItemsPanel
属性,或者......Panel
直接插入ControlTemplate
并将其IsItemsHost
明确地设置为“True”。但是你使用哪个以及为什么?请仔细阅读,找出答案!
在典型的ControlTemplate
中,对于ItemsControl
,例如ListBox
,模板在其中的某处指定了ItemsPresenter
。这是一个简化的摘录,展示了它的使用方法:
<Border x:Name="Bd"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Padding="1" SnapsToDevicePixels="true">
<ScrollViewer Focusable="false" Padding="{TemplateBinding Padding}">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</Border>
如您所见,在模板中间的ItemsPresenter
中指定了ScrollViewer
。然而,你看不到的是一个用于布置物品的实际面板。
因此,如果模板中没有定义面板,它来自何处?这就是ItemsPanel
属性的用武之地。顾名思义,这个属性定义了用于托管和布置项目的面板。然而,它没有说明该面板出现在ControlTemplate
的哪个位置。
这让我们回到了ItemsPresenter
。简而言之,它是一个占位符,基本上说“当设置ItemsPanel
属性时,我将在此处插入该面板并自动将其IsItemsHost
设置为'True'。”
在
ItemsPresenter
模板中使用ItemsControl
的优势在于,您可以让控件的消费者轻松更换面板,而无需完全重新模板化整个控件。
但是,如果您不希望某人能够更改您的面板,因为您的控件取决于某些自定义面板实现以及其他任何会破坏功能,该怎么办?在这种情况下,您不要在模板中使用ItemsPresenter
。您需要指定要使用的确切面板。
这是IsItemsHost
财产发挥作用的地方。当在ControlTemplate
中的面板上设置时,它告诉ItemsControl
使用该特定面板来托管生成的容器,无论ItemsPanel
设置为什么。
这是与上面相同的例子,但它不是ItemsPresenter
,它硬编码SpecializedPanel
来布置项目。我们通过将其IsItemsHost
属性设置为“True”来指示我们要用于托管项目的面板。
<Border x:Name="Bd"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Padding="1" SnapsToDevicePixels="true">
<ScrollViewer Focusable="false" Padding="{TemplateBinding Padding}">
<SpecializedPanel IsItemsHost="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</Border>
在这种情况下,因为模板不使用ItemsPresenter
而是直接包含一个面板,其IsItemsHost
设置为'True',用户无法更改该面板,完全替换整个ControlTemplate
。设置ItemsPanel
属性将基本上被忽略。
回顾一下,如果您是一名控制作者,并希望让您的控件的消费者能够更换用于布置项目的面板,请使用ItemsControl
为您的ItemsPresenter
定义模板。确保还在模板中设置ItemsPanel
属性以指定默认面板。
但是,如果想要“锁定”控件使用的面板,请不要在ItemsPresenter
中使用ControlTemplate
。而是在模板中指定要直接使用的特定面板,然后将其IsItemsHost
属性设置为“True”。
注意:技术上有第三种情况,可以说更常见:你不是一个控制作者创造一些东西供其他用户使用,而只是简单地重新模仿
ItemsControl
(比如说ListBox
)用于某些特殊用途你自己的申请。在这种情况下,由于您是控件的最终消费者,您很可能不必担心下游需要更换面板的其他消费者,因此只需在模板中直接指定面板就完全没问题了(同样,设置它的
IsItemsHost
是真的)并且不担心使用ItemsPresenter
及其相关的ItemsPanel
属性作为后者,虽然有效,但会增加不必要的复杂性而没有任何实际的好处。
希望这能确切地说明发生了什么。