允许多内容自定义控件中的命名元素

时间:2013-12-13 15:03:09

标签: c# wpf custom-controls

要求

让我们从我想要实现的目标开始。我希望有一个带有2列的网格和一个网格分割器(还有一点,但是让它保持简单)。我希望能够在很多不同的地方使用这个网格,所以每次我想创建一个包含两个ContentPresenters的自定义控件时,不要创建它。

最终目标实际上是能够像这样编写XAML:

<MyControls:MyGrid>
    <MyControls:MyGrid.Left>
        <Label x:Name="MyLabel">Something unimportant</Label>
    </MyControls:MyGrid.Left>
    <MyControls:MyGrid.Right>
        <Label>Whatever</Label>
    </MyControls:MyGrid.Right>
</MyControls:MyGrid>

重要提示:请注意,我要将Name应用于Label元素。

尝试1

我做了很多搜索解决方案,我找到的最好的方法是创建一个UserControl以及定义我的网格的XAML文件。这个XAML文件包含2个ContentPresenter元素,并且凭借绑定的魔力,我能够获得一些非常棒的工作。但是,该方法的问题是无法Name嵌套控件,这会导致以下构建错误:

  

无法在元素'MyGrid'上设置名称属性值'MyName'。 'MyGrid'   属于'MyControls'元素的范围,已经有了一个名字   在另一个范围内定义时注册。

手头有这个错误,我回到谷歌博士......

尝试2(当前)

经过更多的搜索后,我在SO上发现了一些信息,表明问题是由于有一个关联的XAML文件和MyGrid类,并且问题应该通过删除XAML并创建所有的问题来解决。通过OnInitialized方法中的代码进行控制。

所以我走了那条路,把它全部编码和编译。好消息是,我现在可以在嵌套的Name控件中添加Label,坏消息是无渲染!不在设计模式下,而不是在运行应用程序时。也没有错误。

所以,我的问题是:我错过了什么?我做错了什么?

我也愿意接受其他方式来满足我的要求。

当前代码

public class MyGrid : UserControl
{
    public static readonly DependencyProperty LeftProperty = DependencyProperty.Register("Left", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
    public object Left
    {
        get { return (object)GetValue(LeftProperty); }
        set { SetValue(LeftProperty, value); }
    }

    public static readonly DependencyProperty RightProperty = DependencyProperty.Register("Right", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
    public object Right
    {
        get { return (object)GetValue(RightProperty); }
        set { SetValue(RightProperty, value); }
    }

    Grid MainGrid;

    static MyGrid()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MyGrid), new FrameworkPropertyMetadata(typeof(MyGrid)));
    }

    protected override void OnInitialized(EventArgs e)
    {
        base.OnInitialized(e);

        //Create control elements
        MainGrid = new Grid();

        //add column definitions
        ColumnDefinition leftColumn = new ColumnDefinition()
        {
            Name = "LeftColumn",
            Width = new GridLength(300)
        };
        MainGrid.ColumnDefinitions.Add(leftColumn);

        MainGrid.ColumnDefinitions.Add(new ColumnDefinition()
        {
            Width = GridLength.Auto
        });

        //add grids and splitter
        Grid leftGrid = new Grid();
        Grid.SetColumn(leftGrid, 0);
        MainGrid.Children.Add(leftGrid);

        GridSplitter splitter = new GridSplitter()
        {
            Name = "Splitter",
            Width = 5,
            BorderBrush = new SolidColorBrush(Color.FromArgb(255, 170, 170, 170)),
            BorderThickness = new Thickness(1, 0, 1, 0)
        };
        MainGrid.Children.Add(splitter);

        Grid rightGrid = new Grid();
        Grid.SetColumn(rightGrid, 1);
        MainGrid.Children.Add(rightGrid);

        //add content presenters
        ContentPresenter leftContent = new ContentPresenter();
        leftContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Left") { Source = this });
        leftGrid.Children.Add(leftContent);

        ContentPresenter rightContent = new ContentPresenter();
        rightContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Right") { Source = this });
        rightGrid.Children.Add(rightContent);

        //Set this content of this user control
        this.Content = MainGrid;
    }
}

1 个答案:

答案 0 :(得分:3)

通过评论进行一些讨论后,很快就发现我的尝试解决方案都不是正确的方法。所以我开始了第三次冒险,希望这个是最终解决方案......而且似乎就是这样!


免责声明:我还没有足够的WPF经验来自信地说我的解决方案是最好的和/或推荐的方法,只是它绝对有效。


首先创建一个新的自定义控件:"Add" > "New Item" > "Custom Control (WPF)"。这将创建一个继承自Control

的新类

在这里,我们将我们的依赖属性绑定到out out content presenters:

public class MyGrid : Control
{
    public static readonly DependencyProperty LeftProperty = DependencyProperty.Register("Left", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
    public object Left
    {
        get { return (object)GetValue(LeftProperty); }
        set { SetValue(LeftProperty, value); }
    }

    public static readonly DependencyProperty RightProperty = DependencyProperty.Register("Right", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
    public object Right
    {
        get { return (object)GetValue(RightProperty); }
        set { SetValue(RightProperty, value); }
    }

    static MyGrid()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(MyGrid), new FrameworkPropertyMetadata(typeof(MyGrid)));
    }
}

当您在Visual Studio中添加此类文件时,它将自动在项目中创建一个新的“Generic.xaml”文件,其中包含此控件的样式以及该样式中的控件模板 - 这是我们定义的控制元素......

<Style TargetType="{x:Type MyControls:MyGrid}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type MyControls:MyGrid}">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="500" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>
                    <Grid Grid.Column="0">
                        <ContentPresenter x:Name="LeftContent" />
                    </Grid>
                    <GridSplitter Width="5" BorderBrush="#FFAAAAAA" BorderThickness="1,0,1,0">
                    </GridSplitter>
                    <Grid Grid.Column="1">
                        <ContentPresenter x:Name="RightContent" />
                    </Grid>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

最后一步是连接2个内容演示者的绑定,然后返回到类文件。

将以下覆盖方法添加到MyGrid类:

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    //Apply bindings and events
    ContentPresenter leftContent = GetTemplateChild("LeftContent") as ContentPresenter;
    leftContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Left") { Source = this });

    ContentPresenter rightContent = GetTemplateChild("RightContent") as ContentPresenter;
    rightContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Right") { Source = this });
}

就是这样!该控件现在可以在其他XAML代码中使用,如下所示:

<MyControls:MyGrid>
    <MyControls:MyGrid.Left>
        <Label x:Name="MyLabel">Something unimportant</Label>
    </MyControls:MyGrid.Left>
    <MyControls:MyGrid.Right>
        <Label>Whatever</Label>
    </MyControls:MyGrid.Right>
</MyControls:MyGrid>

感谢到@NovitchiS的输入,您的建议对于实现这种方法至关重要