none
XAMLのControlTemplateの記述について RRS feed

  • 質問

  • WPFについて勉強中です。

    XAMLのControlTemplateの記述方法について、分からない点がありますので、ご質問させていただきます。

    Xamlでは以下のようにControlTemplateの子要素としてFrameworkElementを指定するかと思います。

    <Button>
        <Button.Template>
            <ControlTemplate>
                <Rectangle Fill="Red"/>
            </ControlTemplate>
        </Button.Template>
    </Button>

    しかし、ControlTemplateクラスの概要(https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.controls.controltemplate?view=netframework-4.8)

    を見るに、そのようなプロパティは存在しません。

    VisualTreeプロパティが、コンテンツプロパティ属性として定義されているかと推測しますが、

    そのクラスは、FrameworkElementFactory型です。

    直観では、TypeConverterが働いているように推測しますが、stiring以外の型から型変換する例などは、どこを探してもありません。

    また、自身でカスタムな型からの型変換を実装しても、「AAAのオブジェクトを型BBBに変換できません。」といったエラーが出ます。

    なぜ、ControlTemplateの子要素としてFrameworkElementを記述できるのでしょうか?

    純粋なXAMLではなく、WPF用に解釈されパース(ビルド)されているのでしょうか?

    ご存じの方がおられましたら、何卒ご回答をよろしくお願いいたします。


    2019年9月18日 17:18

回答

  • 4.0からはXAMLを解析した結果はFrameworkTemplateのVisualTreeプロパティじゃなくTemplateプロパティに適用になってるので…

    以下のコードでXAMLを変換したBamlを解析してみるとTemplateプロパティに適用していることが判ります。

    <Window x:Class="WpfApp1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
    
        <DockPanel>
            <Button DockPanel.Dock="Top" MinWidth="50" MinHeight="50" >
                <Button.Template>
                    <local:ControlTemplate TargetType="{x:Type Button}">
                        <Rectangle Fill="Red"/>
                    </local:ControlTemplate>
                </Button.Template>
            </Button>
    
            <TextBox IsReadOnly="true" x:Name="txb"  VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Margin="0,20,0,0"/>
        </DockPanel>
    </Window>
    namespace WpfApp1
    {
        using System;
        using System.Collections.Generic;
        using System.Windows;
        using System.Linq;
    
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.Loaded += MainWindow_Loaded;
            }
    
            private void MainWindow_Loaded(object sender, RoutedEventArgs e)
            {
                System.Text.StringBuilder sb = new System.Text.StringBuilder();
                System.IO.StringWriter sw = new System.IO.StringWriter(sb);
    
                foreach (string name in this.GetType().Assembly.GetManifestResourceNames())
                {
                    if (!name.EndsWith(".g.resources"))
                    {
                        continue;
                    }
    
                    var rs = this.GetType().Assembly.GetManifestResourceStream(name);
                    var rr = new System.Resources.ResourceReader(rs);
                    foreach (object o in rr)
                    {
                        var de = (System.Collections.DictionaryEntry)o;
                        var key = de.Key as string;
                        var v = de.Value as System.IO.Stream;
                        if (key.EndsWith(".baml"))
                        {
                            sb.AppendLine("### " + name + "\t" + key);
    #if NET30 || NET35
    #else
                            
                            var br = new System.Windows.Baml2006.Baml2006Reader(v);
                            var x = Gekka.Wpf.BamlTest.X.Parse(br, log: sw);
    #endif
                        }
                    }
                }
    
                this.txb.Text = sb.ToString();
            }
        }
    
        class ControlTemplate : System.Windows.Controls.ControlTemplate
        {
            public ControlTemplate()
            {
    #if NET30 || NET35
                var fi = typeof(System.Windows.FrameworkTemplate).GetField("_sealed", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
    #else
                var fi = typeof(System.Windows.FrameworkTemplate).GetField("_hasXamlNodeContent", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
    #endif
                if (fi != null)
                {
                    //ControlTemplateの構成要素がの設定がどこから呼ばれているか調べたい場合
                    //無理やりエラーを発生させてスタックフレームを見てみる
                    //エラーが発生したら呼び出し履歴ウィンドウを見るか、イミディエイトウィンドウでnew System.Diagnostics.StackTrace().GetFrames()
    
                    //System.Diagnostics.Debugger.Launch();
                    //fi.SetValue(this, true);
                }
            }
    
        }
    }
    
    namespace Gekka.Wpf.BamlTest
    {
        using System;
        using System.Collections.Generic;
        using System.Windows;
        using System.Linq;
    
    #if NET30
    #else
    
        //Xamlの要素
        class X
        {
            //XamlReaderやBamlReaderから構造を解析してみる
            public static X Parse(System.Xaml.XamlReader xr, X root = null, int depth = 0, System.IO.TextWriter log = null)
            {
                root = root ?? new X();
    
                StackX stack = new StackX() { Depth = depth, Log = log };
                stack.xs.Push(root);
                stack.Parse(xr);
                return root;
            }
    
            class StackX
            {
                public Stack<XObject> xos { get; } = new Stack<XObject>();
                public Stack<XMember> xms { get; } = new Stack<XMember>();
                public Stack<X> xs { get; } = new Stack<X>();
                public System.IO.TextWriter Log { get; set; }
                public int Depth { get; set; }
    
                public void Parse(System.Xaml.XamlReader xr)
                {
                    while (!xr.IsEof)
                    {
                        switch (xr.NodeType)
                        {
                            case System.Xaml.XamlNodeType.None:
                                break;
                            case System.Xaml.XamlNodeType.StartObject:
                                DebugWrtieLine("StartObject =" + xr.Type.ToString());
                                StartObject(xr);
                                break;
                            case System.Xaml.XamlNodeType.GetObject:
                                DebugWrtieLine("GetObject =[Def]");
                                GetObject(xr);
                                break;
                            case System.Xaml.XamlNodeType.EndObject:
                                EndObject();
                                DebugWrtieLine("EndObject");
                                break;
                            case System.Xaml.XamlNodeType.StartMember:
                                DebugWrtieLine("StartMember =" + xr.Member.ToString());
                                StartMember(xr);
                                break;
                            case System.Xaml.XamlNodeType.EndMember:
                                EndMember();
                                DebugWrtieLine("EndMember");
                                break;
                            case System.Xaml.XamlNodeType.Value:
    
                                if (xr.Value is System.Xaml.XamlNodeList)
                                {
                                    DebugWrtieLine("Value = *** XamlNodeListを展開 ***");
                                    var xv = SetValue(xr);
                                }
                                else
                                {
                                    var xv = SetValue(xr);
                                    DebugWrtieLine("Value =" + xv?.ToString());
                                }
                                break;
                            case System.Xaml.XamlNodeType.NamespaceDeclaration:
                                DebugWrtieLine("Namespace =" + xr.Namespace.Prefix + "=" + xr.Namespace.Namespace.ToString());
                                SetNamespace(xr);
                                break;
                            default:
                                break;
                        }
    
                        xr.Read();
                    }
                }
    
    
                public void StartObject(System.Xaml.XamlReader xr)
                {
                    this.StartObject(new XObject(xr));
                }
    
                public void StartObject(XObject xo)
                {
                    xs.Peek()?.Children.Add(xo);
                    xos.Push(xo);
                    xs.Push(xo);
                }
                public void GetObject(System.Xaml.XamlReader xr)
                {
                    this.GetObject(new XObject(xr));
                }
                public void GetObject(XObject xo)
                {
                    xos.Push(xo);
                    xs.Push(xo);
                }
                public void EndObject()
                {
                    if (!(xs.Peek() is XObject))
                    {
                        throw new ApplicationException("");
                    }
                    xos.Pop();
                    xs.Pop();
                }
    
                public void StartMember(System.Xaml.XamlReader xr)
                {
                    this.StartMember(new XMember(xr));
                }
                public void StartMember(XMember xm)
                {
                    xms.Push(xm);
                    xs.Push(xm);
                    xos.Peek().Members.Add(xm);
                }
                public void EndMember()
                {
                    if (!(xs.Peek() is XMember))
                    {
                        throw new ApplicationException("");
                    }
                    xms.Pop();
                    xs.Pop();
                }
    
                public XValue SetValue(System.Xaml.XamlReader xr)
                {
                    var p = xs.Peek();
                    if (p is XMember)
                    {
                        var xm = (XMember)p;
                        if (xr.Value is System.Xaml.XamlNodeList)
                        {
                            System.Xaml.XamlNodeList nodeList = (System.Xaml.XamlNodeList)xr.Value;
    
                            System.Xaml.XamlReader reader = nodeList.GetReader();
    
                            var nlv = new XNodeListValue(xr);
    
                            X.Parse(nodeList.GetReader(), nlv, this.xs.Count + Depth, this.Log);
                            xm.Value = nlv;
                        }
                        else if (xm.Member.ToString() == "System.Windows.ResourceDictionary.DeferrableContent" && xr.Value is System.IO.MemoryStream)
                        {
                            xm.Value = new XValue("Resourceの展開方法はどうやるんだろう");
                        }
                        else
                        {
                            xm.Value = new XValue(xr);
                        }
    
                        return xm.Value;
                    }
                    else
                    {
                    }
                    return null;
                }
    
                public void SetNamespace(System.Xaml.XamlReader xr)
                {
                    xs.Peek().Namespaces.Add(xr.Namespace);
                }
    
    
                private void DebugWrtieLine(string text)
                {
                    if (Log != null)
                    {
                        Log.WriteLine(new string(' ', (xs.Count + Depth) * 2) + text);
                    }
                }
            }
    
            #region
    
            private X()
            {
            }
    
            public X(System.Xaml.XamlReader xr)
            {
                if (xr != null)
                {
                    this.XamlNodeType = xr.NodeType;
                }
    
            }
            public System.Xaml.XamlNodeType XamlNodeType { get; private set; }
    
            public List<System.Xaml.NamespaceDeclaration> Namespaces { get; } = new List<System.Xaml.NamespaceDeclaration>();
    
            public List<X> Children { get; private set; } = new List<X>();
    
            #endregion
        }
    
        class XObject : X
        {
            public XObject(System.Xaml.XamlReader xr) : base(xr)
            {
                this.Type = xr.Type;
            }
            public System.Xaml.XamlType Type { get; private set; }
    
            public List<XMember> Members { get; } = new List<XMember>();
        }
    
        class XMember : X
        {
            public XMember(System.Xaml.XamlReader xr) : base(xr)
            {
                this.Member = xr.Member;
            }
            public System.Xaml.XamlMember Member { get; private set; }
    
            public XValue Value { get; set; }
        }
    
        class XValue : X
        {
            public XValue(string msg) : base(null)
            {
                this.Value = msg;
            }
    
            public XValue(System.Xaml.XamlReader xr) : this(xr, true)
            {
            }
            protected XValue(System.Xaml.XamlReader xr, bool checkMarkup) : base(xr)
            {
                if (checkMarkup && xr.Value is System.Windows.Markup.MarkupExtension)
                {
                    this.Value = new XMarkup(xr);
                }
                else
                {
                    this.Value = xr.Value;
                }
            }
            public object Value { get; protected set; }
    
            public override string ToString()
            {
                if (Value == null)
                {
                    return "<NULL>";
                }
                else if (Value is XMarkup)
                {
                    return this.Value.ToString();
                }
                else
                {
                    return this.Value.ToString() + " (" + this.Value.GetType().ToString() + ")";
                }
            }
        }
    
        class XMarkup : XValue
        {
            public XMarkup(System.Xaml.XamlReader xr) : base(xr, false)
            {
                this.MarkupExtention = this.Value as System.Windows.Markup.MarkupExtension;
                try
                {
                    this.Value = this.MarkupExtention.ProvideValue(null);
                }
                catch
                {
                }
            }
            public System.Windows.Markup.MarkupExtension MarkupExtention { get; private set; }
    
            public override string ToString()
            {
                return "{" + this.MarkupExtention.GetType().ToString() + " " + this.Value?.ToString() + "}";
            }
        }
    
        class XNodeListValue : XValue
        {
            public XNodeListValue(System.Xaml.XamlReader xr) : base(xr)
            {
            }
        }
    
    #endif
    }

    次にTemplateプロパティにデバッガをひっかけてやると、Create_BamlProperty_FrameworkTemplate_Templateというメソッドを通っていることがわかるので、ソースコードを調べて見てみると、

    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    private WpfKnownMember Create_BamlProperty_FrameworkTemplate_Template()
    {
        Type type = typeof(System.Windows.FrameworkTemplate);
        var bamlMember = new WpfKnownMember(this,  // Schema Context
                        this.GetXamlType(typeof(System.Windows.FrameworkTemplate)), // DeclaringType
                        "Template", // Name
                        typeof(System.Windows.TemplateContent), // type
                        false, // IsReadOnly
                        false // IsAttachable
                                 );
        bamlMember.DeferringLoaderType = typeof(System.Windows.TemplateContentLoader);
        bamlMember.Ambient = true;
        bamlMember.SetDelegate = delegate (object target, object value) { ((System.Windows.FrameworkTemplate)target).Template = (System.Windows.TemplateContent)value; };
        bamlMember.GetDelegate = delegate (object target) { return ((System.Windows.FrameworkTemplate)target).Template; };
        bamlMember.Freeze();
        return bamlMember;
    }

    のようになっていて、FrameworkTemplate.TemplateプロパティにTemplateContentクラスを設定していることがわかる。
    その読み込みにはTemplateContentLoaderというクラスが使われているようなので、そのソースコードを調べてみると、

    public override object Load(XamlReader xamlReader, IServiceProvider serviceProvider)
    {
        if (serviceProvider == null)
        {
            throw new ArgumentNullException("serviceProvider");
        }
        else if (xamlReader == null)
        {
            throw new ArgumentNullException("xamlReader");
        }
    
        IXamlObjectWriterFactory factory = RequireService<IXamlObjectWriterFactory>(serviceProvider);
        return new TemplateContent(xamlReader, factory, serviceProvider);
    }

    のようになっていて、XamlReaderからTemplateContentクラスを作ってることが判ります。
    ここで作られたTemplateContentがFrameworkTemplate.Templateプロパティに適用されるという事になります。

    つまりTypeConverなどの型変換がおこなわれるのではなく、

    • 通常のXAMLはビルド時にBAMLに変換されてリソースに埋め込まれる。
      実行時にBAMLをBaml2006ReaderなどのXamlReaderでXamlの内容を読み出す。
    • BAMLではなく文字列からなどの場合は他のXamlReaderでXamlの内容を読み出す。
    • FrameworkTemplateのXAML子要素に対しては特殊化されていて、TemplateContentLoaderでTemplateContentに変換される。
    • できあがったTemplateContentがFrameworkTemplate.Templateプロパティに適用される。

    という流れになっているのでしょう。

    ContentPropertyAttributeがVisualTreeを指定しているのは、Framework4.0より前はFrameworkTemplate.Templateプロパティが無かったので、名残で残ったままになっているのかもしれない。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク tatalow 2019年9月20日 4:17
    2019年9月19日 16:20

すべての返信

  • この辺りはXAMLと実際のオブジェクトモデルが一致していないですね。
    推測されている通り、表示ツリーを作成するために、VisualTreeプロパティにFrameworkElementFactory型のインスタンスを入れることによって実現されます。コードで書くと以下のような感じです。

    var ct = new ControlTemplate(typeof(Button));
    var rect= new FrameworkElementFactory(typeof(Rectangle));
    ct.VisualTree = rect;

    ★良い回答には質問者は回答済みマークを、閲覧者は投票を!

    2019年9月19日 4:45
    モデレータ
  • ご回答誠にありがとうございます。

    しかしながら、私の疑問点は、XAML上でなぜプロパティとは異なる型(もしくは存在しないプロパティ)を子要素として指定できるのかという点あります。

    XAMLの何かの機能を使用しているのでしょうか?もしくは、WPF用のXAMLとして特別に解析されているのでしょうか?

    引き続き回答を募集いたしますので、ご存じの方がいらっしゃいましたら、ご教示いただけますようよろしくお願いいたします。

    2019年9月19日 12:34
  • 4.0からはXAMLを解析した結果はFrameworkTemplateのVisualTreeプロパティじゃなくTemplateプロパティに適用になってるので…

    以下のコードでXAMLを変換したBamlを解析してみるとTemplateプロパティに適用していることが判ります。

    <Window x:Class="WpfApp1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:WpfApp1"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
    
        <DockPanel>
            <Button DockPanel.Dock="Top" MinWidth="50" MinHeight="50" >
                <Button.Template>
                    <local:ControlTemplate TargetType="{x:Type Button}">
                        <Rectangle Fill="Red"/>
                    </local:ControlTemplate>
                </Button.Template>
            </Button>
    
            <TextBox IsReadOnly="true" x:Name="txb"  VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" Margin="0,20,0,0"/>
        </DockPanel>
    </Window>
    namespace WpfApp1
    {
        using System;
        using System.Collections.Generic;
        using System.Windows;
        using System.Linq;
    
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.Loaded += MainWindow_Loaded;
            }
    
            private void MainWindow_Loaded(object sender, RoutedEventArgs e)
            {
                System.Text.StringBuilder sb = new System.Text.StringBuilder();
                System.IO.StringWriter sw = new System.IO.StringWriter(sb);
    
                foreach (string name in this.GetType().Assembly.GetManifestResourceNames())
                {
                    if (!name.EndsWith(".g.resources"))
                    {
                        continue;
                    }
    
                    var rs = this.GetType().Assembly.GetManifestResourceStream(name);
                    var rr = new System.Resources.ResourceReader(rs);
                    foreach (object o in rr)
                    {
                        var de = (System.Collections.DictionaryEntry)o;
                        var key = de.Key as string;
                        var v = de.Value as System.IO.Stream;
                        if (key.EndsWith(".baml"))
                        {
                            sb.AppendLine("### " + name + "\t" + key);
    #if NET30 || NET35
    #else
                            
                            var br = new System.Windows.Baml2006.Baml2006Reader(v);
                            var x = Gekka.Wpf.BamlTest.X.Parse(br, log: sw);
    #endif
                        }
                    }
                }
    
                this.txb.Text = sb.ToString();
            }
        }
    
        class ControlTemplate : System.Windows.Controls.ControlTemplate
        {
            public ControlTemplate()
            {
    #if NET30 || NET35
                var fi = typeof(System.Windows.FrameworkTemplate).GetField("_sealed", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
    #else
                var fi = typeof(System.Windows.FrameworkTemplate).GetField("_hasXamlNodeContent", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
    #endif
                if (fi != null)
                {
                    //ControlTemplateの構成要素がの設定がどこから呼ばれているか調べたい場合
                    //無理やりエラーを発生させてスタックフレームを見てみる
                    //エラーが発生したら呼び出し履歴ウィンドウを見るか、イミディエイトウィンドウでnew System.Diagnostics.StackTrace().GetFrames()
    
                    //System.Diagnostics.Debugger.Launch();
                    //fi.SetValue(this, true);
                }
            }
    
        }
    }
    
    namespace Gekka.Wpf.BamlTest
    {
        using System;
        using System.Collections.Generic;
        using System.Windows;
        using System.Linq;
    
    #if NET30
    #else
    
        //Xamlの要素
        class X
        {
            //XamlReaderやBamlReaderから構造を解析してみる
            public static X Parse(System.Xaml.XamlReader xr, X root = null, int depth = 0, System.IO.TextWriter log = null)
            {
                root = root ?? new X();
    
                StackX stack = new StackX() { Depth = depth, Log = log };
                stack.xs.Push(root);
                stack.Parse(xr);
                return root;
            }
    
            class StackX
            {
                public Stack<XObject> xos { get; } = new Stack<XObject>();
                public Stack<XMember> xms { get; } = new Stack<XMember>();
                public Stack<X> xs { get; } = new Stack<X>();
                public System.IO.TextWriter Log { get; set; }
                public int Depth { get; set; }
    
                public void Parse(System.Xaml.XamlReader xr)
                {
                    while (!xr.IsEof)
                    {
                        switch (xr.NodeType)
                        {
                            case System.Xaml.XamlNodeType.None:
                                break;
                            case System.Xaml.XamlNodeType.StartObject:
                                DebugWrtieLine("StartObject =" + xr.Type.ToString());
                                StartObject(xr);
                                break;
                            case System.Xaml.XamlNodeType.GetObject:
                                DebugWrtieLine("GetObject =[Def]");
                                GetObject(xr);
                                break;
                            case System.Xaml.XamlNodeType.EndObject:
                                EndObject();
                                DebugWrtieLine("EndObject");
                                break;
                            case System.Xaml.XamlNodeType.StartMember:
                                DebugWrtieLine("StartMember =" + xr.Member.ToString());
                                StartMember(xr);
                                break;
                            case System.Xaml.XamlNodeType.EndMember:
                                EndMember();
                                DebugWrtieLine("EndMember");
                                break;
                            case System.Xaml.XamlNodeType.Value:
    
                                if (xr.Value is System.Xaml.XamlNodeList)
                                {
                                    DebugWrtieLine("Value = *** XamlNodeListを展開 ***");
                                    var xv = SetValue(xr);
                                }
                                else
                                {
                                    var xv = SetValue(xr);
                                    DebugWrtieLine("Value =" + xv?.ToString());
                                }
                                break;
                            case System.Xaml.XamlNodeType.NamespaceDeclaration:
                                DebugWrtieLine("Namespace =" + xr.Namespace.Prefix + "=" + xr.Namespace.Namespace.ToString());
                                SetNamespace(xr);
                                break;
                            default:
                                break;
                        }
    
                        xr.Read();
                    }
                }
    
    
                public void StartObject(System.Xaml.XamlReader xr)
                {
                    this.StartObject(new XObject(xr));
                }
    
                public void StartObject(XObject xo)
                {
                    xs.Peek()?.Children.Add(xo);
                    xos.Push(xo);
                    xs.Push(xo);
                }
                public void GetObject(System.Xaml.XamlReader xr)
                {
                    this.GetObject(new XObject(xr));
                }
                public void GetObject(XObject xo)
                {
                    xos.Push(xo);
                    xs.Push(xo);
                }
                public void EndObject()
                {
                    if (!(xs.Peek() is XObject))
                    {
                        throw new ApplicationException("");
                    }
                    xos.Pop();
                    xs.Pop();
                }
    
                public void StartMember(System.Xaml.XamlReader xr)
                {
                    this.StartMember(new XMember(xr));
                }
                public void StartMember(XMember xm)
                {
                    xms.Push(xm);
                    xs.Push(xm);
                    xos.Peek().Members.Add(xm);
                }
                public void EndMember()
                {
                    if (!(xs.Peek() is XMember))
                    {
                        throw new ApplicationException("");
                    }
                    xms.Pop();
                    xs.Pop();
                }
    
                public XValue SetValue(System.Xaml.XamlReader xr)
                {
                    var p = xs.Peek();
                    if (p is XMember)
                    {
                        var xm = (XMember)p;
                        if (xr.Value is System.Xaml.XamlNodeList)
                        {
                            System.Xaml.XamlNodeList nodeList = (System.Xaml.XamlNodeList)xr.Value;
    
                            System.Xaml.XamlReader reader = nodeList.GetReader();
    
                            var nlv = new XNodeListValue(xr);
    
                            X.Parse(nodeList.GetReader(), nlv, this.xs.Count + Depth, this.Log);
                            xm.Value = nlv;
                        }
                        else if (xm.Member.ToString() == "System.Windows.ResourceDictionary.DeferrableContent" && xr.Value is System.IO.MemoryStream)
                        {
                            xm.Value = new XValue("Resourceの展開方法はどうやるんだろう");
                        }
                        else
                        {
                            xm.Value = new XValue(xr);
                        }
    
                        return xm.Value;
                    }
                    else
                    {
                    }
                    return null;
                }
    
                public void SetNamespace(System.Xaml.XamlReader xr)
                {
                    xs.Peek().Namespaces.Add(xr.Namespace);
                }
    
    
                private void DebugWrtieLine(string text)
                {
                    if (Log != null)
                    {
                        Log.WriteLine(new string(' ', (xs.Count + Depth) * 2) + text);
                    }
                }
            }
    
            #region
    
            private X()
            {
            }
    
            public X(System.Xaml.XamlReader xr)
            {
                if (xr != null)
                {
                    this.XamlNodeType = xr.NodeType;
                }
    
            }
            public System.Xaml.XamlNodeType XamlNodeType { get; private set; }
    
            public List<System.Xaml.NamespaceDeclaration> Namespaces { get; } = new List<System.Xaml.NamespaceDeclaration>();
    
            public List<X> Children { get; private set; } = new List<X>();
    
            #endregion
        }
    
        class XObject : X
        {
            public XObject(System.Xaml.XamlReader xr) : base(xr)
            {
                this.Type = xr.Type;
            }
            public System.Xaml.XamlType Type { get; private set; }
    
            public List<XMember> Members { get; } = new List<XMember>();
        }
    
        class XMember : X
        {
            public XMember(System.Xaml.XamlReader xr) : base(xr)
            {
                this.Member = xr.Member;
            }
            public System.Xaml.XamlMember Member { get; private set; }
    
            public XValue Value { get; set; }
        }
    
        class XValue : X
        {
            public XValue(string msg) : base(null)
            {
                this.Value = msg;
            }
    
            public XValue(System.Xaml.XamlReader xr) : this(xr, true)
            {
            }
            protected XValue(System.Xaml.XamlReader xr, bool checkMarkup) : base(xr)
            {
                if (checkMarkup && xr.Value is System.Windows.Markup.MarkupExtension)
                {
                    this.Value = new XMarkup(xr);
                }
                else
                {
                    this.Value = xr.Value;
                }
            }
            public object Value { get; protected set; }
    
            public override string ToString()
            {
                if (Value == null)
                {
                    return "<NULL>";
                }
                else if (Value is XMarkup)
                {
                    return this.Value.ToString();
                }
                else
                {
                    return this.Value.ToString() + " (" + this.Value.GetType().ToString() + ")";
                }
            }
        }
    
        class XMarkup : XValue
        {
            public XMarkup(System.Xaml.XamlReader xr) : base(xr, false)
            {
                this.MarkupExtention = this.Value as System.Windows.Markup.MarkupExtension;
                try
                {
                    this.Value = this.MarkupExtention.ProvideValue(null);
                }
                catch
                {
                }
            }
            public System.Windows.Markup.MarkupExtension MarkupExtention { get; private set; }
    
            public override string ToString()
            {
                return "{" + this.MarkupExtention.GetType().ToString() + " " + this.Value?.ToString() + "}";
            }
        }
    
        class XNodeListValue : XValue
        {
            public XNodeListValue(System.Xaml.XamlReader xr) : base(xr)
            {
            }
        }
    
    #endif
    }

    次にTemplateプロパティにデバッガをひっかけてやると、Create_BamlProperty_FrameworkTemplate_Templateというメソッドを通っていることがわかるので、ソースコードを調べて見てみると、

    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
    private WpfKnownMember Create_BamlProperty_FrameworkTemplate_Template()
    {
        Type type = typeof(System.Windows.FrameworkTemplate);
        var bamlMember = new WpfKnownMember(this,  // Schema Context
                        this.GetXamlType(typeof(System.Windows.FrameworkTemplate)), // DeclaringType
                        "Template", // Name
                        typeof(System.Windows.TemplateContent), // type
                        false, // IsReadOnly
                        false // IsAttachable
                                 );
        bamlMember.DeferringLoaderType = typeof(System.Windows.TemplateContentLoader);
        bamlMember.Ambient = true;
        bamlMember.SetDelegate = delegate (object target, object value) { ((System.Windows.FrameworkTemplate)target).Template = (System.Windows.TemplateContent)value; };
        bamlMember.GetDelegate = delegate (object target) { return ((System.Windows.FrameworkTemplate)target).Template; };
        bamlMember.Freeze();
        return bamlMember;
    }

    のようになっていて、FrameworkTemplate.TemplateプロパティにTemplateContentクラスを設定していることがわかる。
    その読み込みにはTemplateContentLoaderというクラスが使われているようなので、そのソースコードを調べてみると、

    public override object Load(XamlReader xamlReader, IServiceProvider serviceProvider)
    {
        if (serviceProvider == null)
        {
            throw new ArgumentNullException("serviceProvider");
        }
        else if (xamlReader == null)
        {
            throw new ArgumentNullException("xamlReader");
        }
    
        IXamlObjectWriterFactory factory = RequireService<IXamlObjectWriterFactory>(serviceProvider);
        return new TemplateContent(xamlReader, factory, serviceProvider);
    }

    のようになっていて、XamlReaderからTemplateContentクラスを作ってることが判ります。
    ここで作られたTemplateContentがFrameworkTemplate.Templateプロパティに適用されるという事になります。

    つまりTypeConverなどの型変換がおこなわれるのではなく、

    • 通常のXAMLはビルド時にBAMLに変換されてリソースに埋め込まれる。
      実行時にBAMLをBaml2006ReaderなどのXamlReaderでXamlの内容を読み出す。
    • BAMLではなく文字列からなどの場合は他のXamlReaderでXamlの内容を読み出す。
    • FrameworkTemplateのXAML子要素に対しては特殊化されていて、TemplateContentLoaderでTemplateContentに変換される。
    • できあがったTemplateContentがFrameworkTemplate.Templateプロパティに適用される。

    という流れになっているのでしょう。

    ContentPropertyAttributeがVisualTreeを指定しているのは、Framework4.0より前はFrameworkTemplate.Templateプロパティが無かったので、名残で残ったままになっているのかもしれない。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 回答としてマーク tatalow 2019年9月20日 4:17
    2019年9月19日 16:20
  • ご回答誠にありがとうございます。

    以下の点について理解できました。

    ・XAML上で、本来のプロパティの型とは異なる型を指定してもビルド(xaml→baml)は通る点。

     ※カスタム型で試してみましたが、ビルド可能でした。XAMLのエディタ上ではエラー(波線)が出てますが。。。

    ・xaml/bamlのロード時にTemplateプロパティなどの子要素に対して、特殊なロード処理が走る。

    以上の点を踏まえまして、WPFではxaml/bamlのロード処理にて、純粋なXAMLとしてではなく、

    特殊な解析処理が行われるケースがあるものとして、理解いたしました。

    もう少し、明快な答えがあることを期待しましたが(TypeConvertorが機能しているなど)、そうではないようですね。

    ソースコードの解析までして頂き、大変ありがとうございました。

    本件、いったんクローズとさせていただきますが、他に何かご存じのかたがいらっしゃいましたら、ご回答頂けるとうれしいです。

    2019年9月20日 4:09