BackacheEngineerの技術的な備忘録

技術系でいろいろ書けたらなーと

XAML:UserControl内のコントロールインスタンスにName属性つけれないエラー

WPF を勉強していてまた躓いたので書き記す。

タイトルのとおり、UserControl を XAML で作って内部コードも書いていざメインの XAML で使おうとしたらエラーをはかれた。 どうやら仕様っぽいので今後も躓かないよう気を付ける。

とりあえず、ベターな解決策から言ってしまうと「 UserControl 使わずにカスタムコントロール使え」。

目次

発生していたエラー

下記のように、Name 属性つけれませんとエラーが出ていた。 X とか ? なとこはコントロールの名前とか入る。

要素 'XXXXXX' で Name 属性値 '??????' を設定することはできません。'XXXXXX' は、要素 '@@@@@' のスコープ内にあり、この要素には、別のスコープで定義されたときに既に名前が登録されています。

書いてた UserControl とメインの XAML は下記のとおり。

  • UserControl(XAML
<UserControl x:Class="WindowsXaml_Chapter8.UserControls.ErrorUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WindowsXaml_Chapter8.UserControls"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition x:Name="rowdef1" Height="*" />
            <RowDefinition Height="Auto" />
            <RowDefinition x:Name="rowdef2" Height="0" />
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition x:Name="coldef1" Width="*" MinWidth="100"/>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition x:Name="coldef2" Width="*" MinWidth="100" />
        </Grid.ColumnDefinitions>

        <Grid Name="grid1"
              Grid.Row="0"
              Grid.Column="0"/>

        <Thumb Name="thumb"
               Grid.Row="0"
               Grid.Column="1"
               Width="12"/>

        <Grid Name="grid2"
              Grid.Row="0"
              Grid.Column="2"/>
    </Grid>
</UserControl>
  • UserControl(内部コード、長いのでコンストラクタだけ)
        static ErrorUserControl()
        {
            Child1Property = DependencyProperty.Register(nameof(Child1), typeof(UIElement), typeof(ErrorUserControl), new PropertyMetadata(null, onChildChanged));
            Child2Property = DependencyProperty.Register(nameof(Child2), typeof(UIElement), typeof(ErrorUserControl), new PropertyMetadata(null, onChildChanged));
            OrientationProperty = DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(ErrorUserControl), new PropertyMetadata(Orientation.Horizontal, onOrientationChanged));
            SwapChildrenProperty = DependencyProperty.Register(nameof(SwapChildren), typeof(bool), typeof(ErrorUserControl), new PropertyMetadata(false, onSwapChildrenChanged));
            MinimumSizeProperty = DependencyProperty.Register(nameof(MinimumSize), typeof(double), typeof(ErrorUserControl), new PropertyMetadata((double)200, onMinimumSizeChanged));
        }

        public ErrorUserControl()
        {
            InitializeComponent();
        }
  • メインのXAML(こっちも一部)
        <local_Control:ErrorUserControl x:Name="splitContainer"
                                      Orientation="{Binding Orientation}"
                                      SwapChildren="{Binding SwapEditAndDisplay}"
                                      MinimumSize="200"
                                      Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
            <local_Control:ErrorUserControl.Child1>
                <local:TabbableTextBox x:Name="editBox"
                                       AcceptsReturn="True"
                                       FontSize="{Binding FontSize}"
                                       TabSpaces="{Binding TabSpaces}"
                                       TextChanged="editBox_TextChanged"
                                       SelectionChanged="editBox_SelectionChanged"/>
            </local_Control:ErrorUserControl.Child1>
            <local_Control:ErrorUserControl.Child2>
                <local_Control:RulerContainer x:Name="resultContainer"
                                              ShowRuler="{Binding ShowRuler}"
                                              ShowGridLines="{Binding ShowGridLines}"/>
            </local_Control:ErrorUserControl.Child2>
        </local_Control:ErrorUserControl>

解決策

いろいろ調べた結果、やはりここで困った先駆者様がおり、そこに解決策があった。 大変助かったので引用としてリンクをば。

shuntaro3.hatenablog.com

上記の先駆者様も調べて見つけたのは海外のブログとかなんで、自分ももっとググり力を上げてかないと・・・。

とりあえず、一番はっきりと解決策が書いてあって、かつ信用度の高いのが下記だった。

How to create a WPF UserControl with Named content?

この記事によると、解決策は2つ。

  1. XAML 使わずに UserControl を継承してコードだけでデザインする
  2. カスタムコントロール使う

1の解決策とか本当ににわかには信じ難いが、試しにゴリゴリ書いてみたところ、エラーなくビルドできた。 つまり仕様っぽいという・・・マジかい・・・。

  • SplitContainer2(Visual Studio でクラス追加し、手動で UserControl クラスを継承している)
        #region コンストラクタ
        static SplitContainer2()
        {
            Child1Property = DependencyProperty.Register(nameof(Child1), typeof(UIElement), typeof(SplitContainer2), new PropertyMetadata(null, onChildChanged));
            Child2Property = DependencyProperty.Register(nameof(Child2), typeof(UIElement), typeof(SplitContainer2), new PropertyMetadata(null, onChildChanged));
            OrientationProperty = DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(SplitContainer2), new PropertyMetadata(Orientation.Horizontal, onOrientationChanged));
            SwapChildrenProperty = DependencyProperty.Register(nameof(SwapChildren), typeof(bool), typeof(SplitContainer2), new PropertyMetadata(false, onSwapChildrenChanged));
            MinimumSizeProperty = DependencyProperty.Register(nameof(MinimumSize), typeof(double), typeof(SplitContainer2), new PropertyMetadata((double)200, onMinimumSizeChanged));
        }

        public SplitContainer2()
        {
            //InitializeComponent();

            #region メインGridの設定
            mMainGrid = new Grid();
            mRowDef1 = new RowDefinition
            {
                Height = new GridLength(1, GridUnitType.Star)
            };
            mRowDef2 = new RowDefinition
            {
                Height = new GridLength(1, GridUnitType.Auto)
            };
            mRowDef3 = new RowDefinition
            {
                Height = new GridLength(0)
            };
            mMainGrid.RowDefinitions.Add(mRowDef1);
            mMainGrid.RowDefinitions.Add(mRowDef2);
            mMainGrid.RowDefinitions.Add(mRowDef3);

            mColDef1 = new ColumnDefinition
            {
                Width = new GridLength(1, GridUnitType.Star),
                MinWidth = 100
            };
            mColDef2 = new ColumnDefinition
            {
                Width = new GridLength(1, GridUnitType.Auto)
            };
            mColDef3 = new ColumnDefinition
            {
                Width = new GridLength(1, GridUnitType.Star),
                MinWidth = 100
            };
            mMainGrid.ColumnDefinitions.Add(mColDef1);
            mMainGrid.ColumnDefinitions.Add(mColDef2);
            mMainGrid.ColumnDefinitions.Add(mColDef3);
            #endregion

            #region 子要素の設定
            // grid1
            mGrid1 = new Grid();
            Grid.SetRow(mGrid1, 0);
            Grid.SetColumn(mGrid1, 0);
            // grid2
            mGrid2 = new Grid();
            Grid.SetRow(mGrid2, 0);
            Grid.SetColumn(mGrid2, 2);
            // mThumb
            mThumb = new Thumb
            {
                Width = 12
            };
            mThumb.DragStarted += mThumb_DragStarted;
            mThumb.DragDelta += mThumb_DragDelta;
            Grid.SetRow(mThumb, 0);
            Grid.SetColumn(mThumb, 1);
            #endregion

            mMainGrid.Children.Add(mGrid1);
            mMainGrid.Children.Add(mGrid2);
            mMainGrid.Children.Add(mThumb);

            Content = mMainGrid;
        }
        #endregion

結論

独自 UI 作っていろいろしたいとき、UserControl はやめてカスタムコントロールを使うこと。 確かにコードでデザインすればいいのだが、XAMLのコンセプトから考えて、デザインとコードはできるだけ分離すべきなんで。

それにしても、UserControl 使いまくってツールが出来上がってからこの事実に気づいたら間違いなく発狂する事実だこれ。