65305


Windows10 Pro [1909] OSビルド 18363.720  
VisualStudio Community 2019 [Ver 16.5.2]  
.NetFramework 4.8.03752

* * *

WPFの構造についての質問です。

現在、カスタムコントロールを作成しているのですが  
自作の構造体型の依存関係プロパティを持ったオブジェクトがあります。  
これをカスタムコントロールのTemplate内からBindingしており  
実行時にも想定通り動作します。

しかし、分からないのはこのカスタムコントロールを使用する側のデザイナ実行時の挙動で、  
依存関係プロパティに値を設定しても即時反映されません。

もう少し詳しく言うと、Name, Item, Remarkという3つのobjectを持てるコントロールで  
それぞれをGridの左、中、右に配置されるようなコントロールになります。  
Xamlは以下の通りです。

[code]

        <Style TargetType="{x:Type local:ItemSet}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type local:ItemSet}">
                        <Border Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}">
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition x:Name="ItemNameColumn"
                                                      Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:ItemSet}, Path=GridDefinitions.First}"/>
                                    <ColumnDefinition x:Name="ItemColumn"
                                                      Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:ItemSet}, Path=GridDefinitions.Second}"/>
                                    <ColumnDefinition x:Name="RemarkColumn"
                                                      Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=local:ItemSet}, Path=GridDefinitions.Third}"/>
                                </Grid.ColumnDefinitions>
    
                                <ContentPresenter Grid.Column="0" Content="{TemplateBinding ItemName}"/>
    
                                <ContentPresenter Grid.Column="1" Content="{TemplateBinding Item}"/>
    
                                <ContentPresenter Grid.Column="2" Content="{TemplateBinding Remark}"/>
                            </Grid>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    
[/code]

そしてコード側

[code]

        [ContentProperty(nameof(Item))]
        public class ItemSet : ContentControl
        {
            static ItemSet()
            {
                DefaultStyleKeyProperty.OverrideMetadata(typeof(ItemSet), new FrameworkPropertyMetadata(typeof(ItemSet)));
            }
    
            public static readonly DependencyProperty ItemNameProperty =
                DependencyProperty.Register(
                    "ItemName",
                    typeof(object),
                    typeof(ItemSet),
                    new PropertyMetadata());
    
            public static readonly DependencyProperty ItemProperty =
                DependencyProperty.Register(
                    "Item",
                    typeof(object),
                    typeof(ItemSet),
                    new PropertyMetadata());
    
            public static readonly DependencyProperty RemarkProperty =
                DependencyProperty.Register(
                    "Remark",
                    typeof(object),
                    typeof(ItemSet),
                    new PropertyMetadata());
    
            public static readonly DependencyProperty GridDefinitionsProperty =
                DependencyProperty.Register(
                    "GridDefinitions",
                    typeof(Definitions),
                    typeof(ItemSet),
                    new PropertyMetadata());
    
    
            public object ItemName
            {
                get => GetValue(ItemNameProperty);
                set => SetValue(ItemNameProperty, value);
            }
    
            public object Item
            {
                get => GetValue(ItemProperty);
                set => SetValue(ItemProperty, value);
            }
    
            public object Remark
            {
                get => GetValue(RemarkProperty);
                set => SetValue(RemarkProperty, value);
            }
    
            public Definitions GridDefinitions
            {
                get => (Definitions)GetValue(GridDefinitionsProperty);
                set => SetValue(GridDefinitionsProperty, value);
            }
        }
    
[/code]

やりたいことはGridDefinitionsプロパティ値に応じて  
3つのカラムの幅を変更するというものです。

例えば、使用側から「1*,2*,3*」という風に指定した場合  
それぞれのカラムのWidthプロパティにカンマ区切りの値が適用されます。

Definitions構造体とTypeConverterは以下の通り定義しています。

[code]

        [TypeConverter(typeof(DefinitionsTypeConverter))]
        public struct Definitions
        {
            public GridLength First { get; set; }
            public GridLength Second { get; set; }
            public GridLength Third { get; set; }
    
            public Definitions(GridLength first, GridLength second, GridLength third)
            {
                First = first;
                Second = second;
                Third = third;
            }
        }
    
[/code]

[code]

    public class DefinitionsTypeConverter : TypeConverter
        {
            public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
            {
                return sourceType == typeof(string);
            }
    
            public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
            {
                var str = (string)value;
                var spl = str.Split(',');
                var ary = spl
                    .Select(x =>
                    {
                        if (x == "Auto")        return new GridLength(0, GridUnitType.Auto);
                        if (x.Last() == '*')    return new GridLength(double.Parse(x.Substring(0, x.Length - 1)), GridUnitType.Star);
                        else                    return new GridLength(double.Parse(x), GridUnitType.Pixel);
                    })
                    .ToArray();
                return new Definitions(ary[0], ary[1], ary[2]);
            }
        }
    
[/code]

この状態で使用側から先ほどの例と同じ値を指定した時、  
ビルド直後はデザイナ上「1*,1*,1*」の状態で表示されます。  
しかし、このうちひとつの値を変更した時、値が反映され、  
それぞれの割合で表示されるようになります。

もちろん、ビルドは正常に通っており、実行時は想定通りの動作をするのですが  
なぜデザイン時にこのような挙動になってしまうのでしょうか。

プロパティの方が構造体であることが原因かとも思いましたが  
例えばThisckness構造体であるMarginプロパティでは  
このような現象がおこりませんし、どのような構造になっているのか気になっています。

色々と知識不足であるとは思いますが、ご教示いただければと思います。


CC BY-SA 4.0
2020-03-30T08:17:49.513
65234
2020-04-02T09:53:55.943
2020-04-02T06:36:34.230
38100
38100
question
c# wpf xaml
構造体Binding時のデザイナ挙動について


おそらくですが、`TemplateBinding`と`Binding`の反映タイミングの違いによる現象かと思われます。ColumnDefinitionへの反映が遅れるため、ColumnDefinitionの初期値の「1*」で表示される時間があるように思えます。

解決策として思いつくのは

  * バインドを`TemplateBinding`で統一する
  * カスタムコントロールを UserControl で作り直し、ControlTemplateを使わない作りにする

といったところでしょうか。


CC BY-SA 4.0
2020-04-02T09:53:55.943
65305
2020-04-02T09:53:55.943
14817
65234
answer