0

I have this XAML:

<dxb:BarStaticItem>
  <TextBlock Text="{Binding MyStatusBarText}"></TextBlock>
</dxb:BarStaticItem> 

However, I'm getting this error:

Cannot add content to an object of type BarStaticItem

How do I fix this, so I can do things like change the color and style of the rendered item?

4

2 回答 2

5

假设BarStaticItem是一个UserControl...

我在一个部分cs文件中使用后面的代码,其中(几乎)一切都完成了,而是使用ObservableCollectionof UIElement(或您想要的任何元素)

1)创建一个相关的部分类BarStaticItem.Children.cs,然后添加所需的命名空间:

using System.Collections.ObjectModel; // ObservableCollection.
using System.Collections.Specialized; // NotifyCollectionChangedEventHandler.
using System.Windows.Markup; // [ContentProperty()]

2)ContentProperty在部分类声明上方添加一个标志,然后添加您的Children属性声明:

namespace YourNamespace.SubNamespace
{
    [ContentProperty("Children")] // <- here !
    public partial class BarStaticItem
    // (This is the related BarStaticItem.Children.cs file)
    {
        /// <summary>
        /// Gets the Children Property of this BarStaticItem.
        /// </summary>
        public ObservableCollection<UIElement> Children { get; private set; }
    }
}

3) 现在在 cs 文件的私有方法中创建 ObservableCollection 属性初始化程序:

        private void RegisterChildrenObservation()
        {
            Children = new ObservableCollection<UIElement>();
            Children.CollectionChanged += 
                new NotifyCollectionChangedEventHandler(Children_CollectionChanged);
        }

4) 在自定义 UI 元素的构造函数中调用该初始化程序:

namespace YourNamespace.SubNamespace
{
    public partial class BarStaticItem : UserControl
    // (This is the base BarStaticItem.xaml.cs file)
    {
        public BarStaticItem()
        {
            InitializeComponent();
            RegisterChildrenObservation(); // <- here !
        }
    }
}

5)通过声明您在初始化程序中调用的方法处理程序来处理 BarStaticItem.Children.cs 文件中的 Children 集合的行为:

这只是简单的正式程序。一旦你理解了整个事情,你会发现你可以玩弄它并创建比单独使用 xaml 所能做的更多的场景。首先,这里只有两个状态真正重要:

  • NotifyCollectionChangedAction.Add
  • NotifyCollectionChangedAction.Remove

    private void Children_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
            {
                foreach (UIElement currentElement in e.NewItems)
                {
                    MyChildContainer.Children.Add(currentElement);
                }
                break;
            }
    
            case NotifyCollectionChangedAction.Move:
            {
                break;
            }
    
            case NotifyCollectionChangedAction.Remove:
            {
                foreach (UIElement currentElement in e.OldItems)
                {
                    MyChildContainer.Children.Remove(currentElement);
                }
                break;
            }
    
            case NotifyCollectionChangedAction.Replace:
            {
                break;
            }
    
            case NotifyCollectionChangedAction.Reset:
            {
                break;
            }
    
            default:
            {
                break;
            }
        }
    }
    

6) 您几乎完成了,但您必须在 BarStaticItem.xaml 文件中创建并命名一个 UIElement 以包含添加的 UIElements :

<UserControl ...>
    <Grid 
        x:Name="MyChildContainer"><!-- HERE ! -->
    </Grid>
</UserControl>

然后你就完成了,你可以像你做的那样直接在 XAML 中将任何子元素添加到你的 BarStaticItem。

<dxb:BarStaticItem>
    <TextBlock Text="{Binding MyStatusBarText}"></TextBlock>
</dxb:BarStaticItem>

..这TextBlockGrid落在MyChildContainer. BarStaticItem.xaml您可以使用 DockPanel 或 StackPanel,甚至根据情况下的类型或更新(依赖)属性决定子元素将进入哪个容器NotifyCollectionChangedAction.Add,例如:

private void Children_CollectionChanged(
    object sender, NotifyCollectionChangedEventArgs e)
{
    switch (e.Action)
    {
        case NotifyCollectionChangedAction.Add:
        {
            foreach (UIElement currentElement in e.NewItems)
            {
                if (currentElement.GetType() == typeof(TextBlock))
                {
                    TextBlock currentTextBlock = (TextBlock)currentElement;
                    // Manipulate your TextBlock...
                    HeaderTextBlockContainer.Children.Add(currentElement);
                }
                else if (currentElement.GetType() == typeof(Button))
                {
                    FooterButtonsDockPanel.Children.Add(currentElement);
                    DockPanel.SetDock(currentElement, Dock.Right);
                }
                else
                {
                    MainContentContainer.Children.Add(currentElement);
                }
                ContentDefined = true; // Custom Property.
            }
            //...

谁DV你不是我

于 2015-01-18T07:29:08.107 回答
0

This XAML will work fine, but its limiting (it won't allow you to set the color of the text displayed, as requested):

<dxb:BarStaticItem
    Content="{Binding MyStatusBarText}">
</dxb:BarStaticItem> 

This particular control does allow us to set the ContentTemplate. We can use this to style the content:

<dxb:BarStaticItem
   ContentTemplate="????">
</dxb:BarStaticItem>   

First, we define a DataTemplate in Window.Resources. This is what our ContentTemplate will point at:

<Window.Resources>
    <DataTemplate x:Key="MyStatusBarContentTemplate">
        <!-- As the DataContext of a resource does not default to the window, we have to use RelativeSource to find the window. -->
        <TextBlock Name="MyText"                
            Text="{Binding Path=MyStatusBarText, 
            RelativeSource={RelativeSource Mode=FindAncestor,
            AncestorType=Window}}">                
        </TextBlock>
    </DataTemplate>
</Window.Resources>

As the DataContext of a DataTemplate is different to the rest of the XAML, if we omit the XAML RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window} then the binding will not work properly. Other than that, all we are doing is defining a template which can be used to render the contents of the control.

Then, we point our control at this DataTemplate:

<dxb:BarStaticItem
    ContentTemplate="{DynamicResource MyStatusBarContentTemplate}">
</dxb:BarStaticItem>

Now that we have defined a custom data template, we can do anything we want. For example, we could add a Converter which colored the text red if the status bar contained the text Error (something that was impossible, otherwise).

This answer also illustrates how it is possible to use a DataTemplate to display custom content for most controls.

Update

Rather than defining the DataTemplate in the resources for the Window, defined it as a resource for BarStaticItem. This keeps related items together in the XAML.

This particular XAML means that the status bar text automatically goes red if the text contains the string Error, and the status bar text is automatically prefixed with the time. Let me know if you want me to post the C# code for the converters.

<dxb:BarStaticItem
  ContentTemplate="{DynamicResource MyStatusBarContentTemplate}">
  <dxb:BarStaticItem.Resources>
      <DataTemplate x:Key="MyStatusBarContentTemplate">
          <!-- As the DataContext of a resource does not default to the window, we have to use RelativeSource to find the window. -->
          <TextBlock Name="MyText"
              Foreground="{Binding ElementName=MyText, Path=Text, Converter={StaticResource ColorRedIfTextContainsError}}"
              Text="{Binding Path=SettingsGlobalViewModel.StatusBarText, 
              RelativeSource={RelativeSource Mode=FindAncestor,
              AncestorType=Window},
              Converter={StaticResource PrefixStringWithTime}}">
          </TextBlock>
      </DataTemplate>
  </dxb:BarStaticItem.Resources>
</dxb:BarStaticItem> 
于 2015-01-17T14:45:41.320 回答