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

标签 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 元素,借助绑定(bind)的魔力,我能够让一些东西正常工作,这非常棒。但是,该方法的问题是无法Name 嵌套控件,这会导致以下构建错误:

Cannot set Name attribute value 'MyName' on element 'MyGrid'. 'MyGrid' is under the scope of element 'MyControls', which already had a name registered when it was defined in another scope.

有了那个错误,我回到了 Dr. Google...

尝试 2(当前)

经过大量搜索后,我在这里找到了一些关于 SO 的信息,这些信息表明问题是由于具有与 MyGrid 类关联的 XAML 文件,并且应该可以通过删除 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;
    }
}

最佳答案

通过评论进行一些讨论后,很快就清楚我尝试的两种解决方案都不是正确的解决方法。所以我开始了第三次冒险,希望这一次能成为最终的解决方案……现在看来确实如此!


免责声明:我还没有足够的 WPF 经验来自信地说我的解决方案是执行此操作的最佳和/或推荐方法,只是它确实有效。


首先创建一个新的自定义控件:“添加”>“新建项”>“自定义控件 (WPF)”。这将创建一个继承自 Control 的新类。

在这里,我们将我们的依赖属性用于绑定(bind)到内容呈现器:

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 个内容展示器的绑定(bind),回到类文件。

将以下覆盖方法添加到 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 的意见,您的建议对于让这种方法发挥作用至关重要

关于c# - 允许多内容自定义控件中的命名元素,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20569563/

相关文章:

jquery - 页面加载时滚动到给定的 jCarousel 幻灯片

c# - 自定义 Xamarin 表单控件的数据绑定(bind)失败,但适用于普通控件

c# - 通过 WPAD 中定义的代理与 Web 服务通信

WPF - 在 xaml 中完成另一个动画后开始动画

c# - 如何将某些颜色分类到颜色范围?

c# - 如何读取 Build-Action : Resource items

wpf - 依赖属性继承

c# - LINQ 从多条记录中获取最新记录到列表

c# - mvc3 RedirectToAction

c# - 从反射抛出的 TargetInvocationException 是否可以有 null InnerException