dhq_boiler
@dhq_boiler

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

ContentTemplate内の要素を取得したい

Q&A

Closed

解決したいこと

UserControlの中でContentTemplateを定義しています。そのContentTemplateで定義している要素(NameがWidthCellのDockPanel)をプロパティの中からアクセスして取得したいのですが、FindName(string)を実行すると、nullが返ってきます...。また巷で流布されているFindVisualChildren()を使ってみても、目的の要素は取得できません。どうしたら取得できるでしょうか?

DetailPie -(use)-> DetailPathGeometry -(include)-> DockPanel(WidthCell)という関係です。

DetailPie.cs
using boilersGraphics.Controls;
using boilersGraphics.Extensions;
using boilersGraphics.ViewModels;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Shapes;
using static boilersGraphics.Views.DetailPathGeometry;

namespace boilersGraphics.Views
{
    public class DetailPie : UserControl
    {
        private DetailPathGeometry _detailPathGeometry;
        public DetailPie()
        {
            Name = "DetailPie";
            Loaded += DetailPie_Loaded;
        }

        private void DetailPie_Loaded(object sender, RoutedEventArgs e)
        {
            var dockPanel = new DockPanel();
            var childDockPanel = new DockPanel();
            childDockPanel.SetValue(DockPanel.DockProperty, Dock.Bottom);
            var grandchildDockPanel = new DockPanel();
            grandchildDockPanel.SetValue(DockPanel.DockProperty, Dock.Right);
            var button = new Button();
            button.Width = 100;
            button.Margin = new System.Windows.Thickness(5);
            button.HorizontalAlignment = HorizontalAlignment.Right;
            button.SetBinding(ButtonBase.CommandProperty, "OKCommand");
            button.Content = "OK";
            grandchildDockPanel.Children.Add(button);
            childDockPanel.Children.Add(grandchildDockPanel);
            dockPanel.Children.Add(childDockPanel);
            _detailPathGeometry = new DetailPathGeometry();
            _detailPathGeometry.Name = "DetailPathGeometry";
            _detailPathGeometry.SetBinding(FrameworkElement.DataContextProperty, "ViewModel.Value");
            _detailPathGeometry.CenterVisibility = Visibility.Collapsed;
            _detailPathGeometry.Stretch = System.Windows.Media.Stretch.Fill;
            _detailPathGeometry.SetBinding(WidthPlacementProperty, new Binding("WidthPlacement") { Source = this });
            var canvas = new Canvas();
            var centerPoint = new Ellipse();
            centerPoint.Width = 5;
            centerPoint.Height = 5;
            centerPoint.Fill = Brushes.Red;
            var viewModel = ((DataContext as DetailPieViewModel).ViewModel.Value as NPieViewModel);
            centerPoint.SetValue(Canvas.LeftProperty, viewModel.PieCenterPoint.Value.X - viewModel.Left.Value + 100);
            centerPoint.SetValue(Canvas.TopProperty, viewModel.PieCenterPoint.Value.Y - viewModel.Top.Value + 100);
            canvas.Children.Add(centerPoint);
            var stackPanel = new StackPanel();
            stackPanel.Orientation = Orientation.Horizontal;
            stackPanel.SetValue(Canvas.LeftProperty, viewModel.PieCenterPoint.Value.X - viewModel.Left.Value + 115);
            stackPanel.SetValue(Canvas.TopProperty, viewModel.PieCenterPoint.Value.Y - viewModel.Top.Value + 115);
            var style = new Style();
            style.TargetType = typeof(DoubleTextBox);
            var setter = new Setter();
            setter.Property = WidthProperty;
            setter.Value = 40;
            style.Setters.Add(setter);
            stackPanel.Resources.Add(string.Empty, style);
            var label = new Label();
            label.Foreground = Brushes.Red;
            label.Content = "Center Point";
            stackPanel.Children.Add(label);
            var doubleTextBox = new DoubleTextBox();
            doubleTextBox.Foreground = Brushes.Red;
            doubleTextBox.SetBinding(DoubleTextBox.DoubleTextProperty, "PieCenterPoint.Value.X");
            stackPanel.Children.Add(doubleTextBox);
            var doubleTextBox2 = new DoubleTextBox();
            doubleTextBox2.Foreground = Brushes.Red;
            doubleTextBox2.SetBinding(DoubleTextBox.DoubleTextProperty, "PieCenterPoint.Value.Y");
            stackPanel.Children.Add(doubleTextBox2);
            canvas.Children.Add(stackPanel);
            _detailPathGeometry.Content = canvas;
            dockPanel.Children.Add(_detailPathGeometry);
            Content = dockPanel;
        }


        public Placement WidthPlacement
        {
            get
            {
                var detailPathGeometry = _detailPathGeometry;
                var children = this.FindVisualChildren<DependencyObject>().ToList();
                var target = children.FirstOrDefault(x => x is FrameworkElement xx && xx.Name == "WidthCell");
                // target is null!!!

                var obj = detailPathGeometry.FindName("WidthCell");
                // obj is null!!!

                // コードが続きます...

                return Placement.Top; //仮設定
            }
        }
    }
}

DetailPathGeometry.xaml
<UserControl x:Class="boilersGraphics.Views.DetailPathGeometry"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:control="clr-namespace:boilersGraphics.Controls"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:c="clr-namespace:CalcBinding;assembly=CalcBinding"
             x:Name="userControl"
             mc:Ignorable="d">
    <UserControl.ContentTemplate>
        <DataTemplate>
            <Grid>
                <Grid>
                    <Grid.Resources>
                        <Style TargetType="control:DoubleTextBox">
                            <Setter Property="Width" Value="40" />
                        </Style>
                    </Grid.Resources>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="100" />
                        <ColumnDefinition Width="1" />
                        <ColumnDefinition Width="{Binding DataContext.Width.Value, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />
                        <ColumnDefinition Width="1" />
                        <ColumnDefinition Width="100" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="100" />
                        <RowDefinition Height="1" />
                        <RowDefinition Height="{Binding DataContext.Height.Value, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />
                        <RowDefinition Height="1" />
                        <RowDefinition Height="100" />
                    </Grid.RowDefinitions>
                    <Line Grid.Row="1"
                          Grid.Column="1"
                          Grid.ColumnSpan="3"
                          Margin="-20"
                          Stretch="Fill"
                          Stroke="Red"
                          StrokeDashArray="2 2"
                          X2="1" />
                    <Line Grid.Row="3"
                          Grid.Column="1"
                          Grid.ColumnSpan="3"
                          Margin="-20"
                          Stretch="Fill"
                          Stroke="Red"
                          StrokeDashArray="2 2"
                          X2="1" />
                    <Line Grid.Row="1"
                          Grid.RowSpan="3"
                          Grid.Column="1"
                          Margin="-20"
                          Stretch="Fill"
                          Stroke="Red"
                          StrokeDashArray="2 2"
                          Y2="1" />
                    <Line Grid.Row="1"
                          Grid.RowSpan="3"
                          Grid.Column="3"
                          Margin="-20"
                          Stretch="Fill"
                          Stroke="Red"
                          StrokeDashArray="2 2"
                          Y2="1" />
                    <Path Grid.Row="2"
                          Grid.Column="2"
                          Data="{Binding DataContext.PathGeometry.Value, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
                          Fill="{Binding DataContext.FillColor.Value, Converter={StaticResource solidColorBrushConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
                          IsHitTestVisible="False"
                          Stretch="{Binding Stretch, ElementName=userControl}"
                          Stroke="{Binding DataContext.EdgeColor.Value, Converter={StaticResource solidColorBrushConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
                          StrokeThickness="{Binding DataContext.EdgeThickness.Value, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />
                    <Ellipse Grid.Row="2"
                             Grid.Column="2"
                             Width="5"
                             Height="5"
                             Fill="Red"
                             Visibility="{Binding CenterVisibility, ElementName=userControl}" />
                </Grid>
                <Grid>
                    <Grid.Resources>
                        <Style TargetType="control:DoubleTextBox">
                            <Setter Property="Width" Value="40" />
                        </Style>
                    </Grid.Resources>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="85" />
                        <ColumnDefinition Width="31" />
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="31" />
                        <ColumnDefinition Width="85" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="85" />
                        <RowDefinition Height="31" />
                        <RowDefinition Height="*" />
                        <RowDefinition Height="31" />
                        <RowDefinition Height="85" />
                    </Grid.RowDefinitions>
                    <DockPanel Grid.Row="1" Grid.Column="0">
                        <StackPanel HorizontalAlignment="Center"
                                    VerticalAlignment="Center"
                                    Orientation="Horizontal">
                            <Label Content="Top" />
                            <control:DoubleTextBox VerticalContentAlignment="Center" DoubleText="{Binding DataContext.Top.Value, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />
                        </StackPanel>
                    </DockPanel>
                    <DockPanel Grid.Row="0"
                               Grid.Column="1"
                               Grid.ColumnSpan="2">
                        <StackPanel HorizontalAlignment="Left"
                                    VerticalAlignment="Center"
                                    Orientation="Horizontal">
                            <Label Content="Left" />
                            <control:DoubleTextBox VerticalContentAlignment="Center" DoubleText="{Binding DataContext.Left.Value, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />
                        </StackPanel>
                        <StackPanel HorizontalAlignment="Center"
                                    VerticalAlignment="Center"
                                    Orientation="Horizontal">
                            <Label Content="Rotate" />
                            <control:DoubleTextBox VerticalContentAlignment="Center" DoubleText="{Binding DataContext.RotationAngle.Value, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />
                            <Label Content="°" />
                        </StackPanel>
                    </DockPanel>
                    <DockPanel Name="WidthCell"
                               Grid.Row="{c:Binding WidthRow, ElementName=userControl}"
                               Grid.Column="2">
                        <StackPanel HorizontalAlignment="Center"
                                    VerticalAlignment="Top"
                                    Orientation="Horizontal">
                            <Label Content="Width" />
                            <control:DoubleTextBox VerticalContentAlignment="Center" DoubleText="{Binding DataContext.Width.Value, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />
                        </StackPanel>
                    </DockPanel>
                    <DockPanel Grid.Row="2" Grid.Column="4">
                        <StackPanel HorizontalAlignment="Left"
                                    VerticalAlignment="Center"
                                    Orientation="Horizontal">
                            <Label Content="Height" />
                            <control:DoubleTextBox VerticalContentAlignment="Center" DoubleText="{Binding DataContext.Height.Value, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}" />
                        </StackPanel>
                    </DockPanel>
                    <DockPanel Grid.Row="2"
                               Grid.Column="2"
                               Margin="0,50,0,0"
                               Visibility="{Binding CenterVisibility, ElementName=userControl}">
                        <StackPanel HorizontalAlignment="Center"
                                    VerticalAlignment="Center"
                                    Orientation="Horizontal">
                            <Label Background="White"
                                   Content="Center"
                                   FontWeight="Bold"
                                   Foreground="Red" />
                            <control:DoubleTextBox Margin="5,0,5,0"
                                                   VerticalContentAlignment="Center"
                                                   DoubleText="{Binding DataContext.CenterX.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
                                                   Foreground="Red" />
                            <control:DoubleTextBox Margin="5,0,5,0"
                                                   VerticalContentAlignment="Center"
                                                   DoubleText="{Binding DataContext.CenterY.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}}"
                                                   Foreground="Red" />
                        </StackPanel>
                    </DockPanel>
                </Grid>
                <ContentPresenter x:Name="Content" Content="{Binding ElementName=userControl, Path=Content}"/>
            </Grid>
        </DataTemplate>
    </UserControl.ContentTemplate>
</UserControl>

Extensions.cs
        public static IEnumerable<T> FindVisualChildren<T>(this DependencyObject depObj) where T : DependencyObject
        {
            if (depObj != null)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                    if (child != null)
                    {
                        yield return (T)child;
                    }

                    foreach (T childOfChild in FindVisualChildren<T>(child))
                    {
                        yield return childOfChild;
                    }
                }
            }
        }

この問題の背景

現在、当プロジェクトboiler's Graphicsは扇形を描画するツール、その名も「扇形ツール」を開発しています。扇形ツールの描画する機能は開発できたのですが、描画した後、その図形の詳細情報を閲覧する機能を開発しようとしました。まずはその画面をご覧ください。

2022-01-07.png

赤い点が扇形の中心点になります。もう一枚ご覧ください。

2022-01-07 (1).png

向きが逆になっています。扇形ツールは任意の角度で描画できるため、中心点が上にきたり下にきたりします。するとWidthが赤点やCenter Pointに重なってしまうのがわかるでしょうか?私は中心点が上にきたらWidthを図形の下へ表示させたい、あるいは中心点が下にきたらWidthを図形の上へ表示させたいのです。(今はWidthを上辺に固定しています。)

そこで機能するはずなのが、DetailPieクラスのWidthPlacementプロパティです。このプロパティの中でWidthと中心点のHitTestを実施し、重なっていれば対岸へ追いやりたいと思っています。

該当するソースコード

ブランチ:feature/#25

0

4Answer

Comments

  1. @dhq_boiler

    Questioner

    ん~、ダメみたいです。ContentPresenterを取得したとしてもContentTemplateがnullになってますね。


    public Placement WidthPlacement
    {
    get
    {
    var detailPathGeometry = _detailPathGeometry;
    var contentPresenter = FindVisualChild<ContentPresenter>(this);
    DataTemplate dataTemplate = contentPresenter.ContentTemplate; //dataTemplate is null!!!
    var target = dataTemplate.FindName("WidthCell", contentPresenter); //NullReferenceException

    // コードが続きます...

    return Placement.Top;
    }
    }

    private T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
    {
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
    {
    DependencyObject child = VisualTreeHelper.GetChild(obj, i);
    if (child != null && child is T)
    return (T)child;
    else
    {
    T childOfChild = FindVisualChild<T>(child);
    if (childOfChild != null)
    return childOfChild;
    }
    }
    return null;
    }

DataTemplateは取得できるようになりました。

        public Placement WidthPlacement
        {
            get
            {
                var detailPathGeometry = _detailPathGeometry;
                detailPathGeometry.ApplyTemplate();
                var contentPresenter = FindVisualChild<ContentPresenter>(detailPathGeometry);
                DataTemplate dataTemplate = contentPresenter.ContentTemplate;
                var target = dataTemplate.FindName("WidthCell", contentPresenter); // InvalidOperationException このテンプレートが適用されている要素上でのみ、この操作は有効です。

                // コードが続きます...

                return Placement.Top;
            }
        }

        private T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                if (child != null && child is T)
                    return (T)child;
                else
                {
                    T childOfChild = FindVisualChild<T>(child);
                    if (childOfChild != null)
                        return childOfChild;
                }
            }
            return null;
        }

しかし、FindNameメソッドの呼び出しで以下のような例外が発生しています。

'System.InvalidOperationException' (PresentationFramework.dll の中)
このテンプレートが適用されている要素上でのみ、この操作は有効です。

これの回避策は何かありますでしょうか?

0Like

解決策わかりました!
ContentPresenterをFindVisualChildrenメソッドで取得して、ContentTemplateにアクセスしてDataTemplateを取得したら、LoadContentメソッドでテンプレートのコンテンツを取得します。その後、再びFindVisualChildren()でDockPanelリストを取得し、FirstメソッドでNameを指定して絞り込みます。これで晴れてContentTemplate内の要素を取得することができました。

回答してくれた方、ありがとうございました。

        public Placement WidthPlacement
        {
            get
            {
                var detailPathGeometry = _detailPathGeometry;
                detailPathGeometry.ApplyTemplate();
                var contentPresenter = detailPathGeometry.FindVisualChildren<ContentPresenter>().ToList().First();
                DataTemplate dataTemplate = contentPresenter.ContentTemplate;
                var obj = dataTemplate.LoadContent();
                var x = obj.FindVisualChildren<DockPanel>().First(x => x.Name == "WidthCell");
                
                // コードが続きます...

                return Placement.Top;
            }
        }
0Like

FindVisualChildren拡張メソッドを改良しました。このメソッドを実行することにより、ContentTemplate内で定義された要素も検索できるようになります。

        public static IEnumerable<T> FindVisualChildren<T>(this DependencyObject depObj) where T : DependencyObject
        {
            if (depObj != null)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                    if (child != null && child is T)
                    {
                        yield return (T)child;
                    }

+                   if (child != null && child is ContentPresenter cp)
+                   {
+                       var dependencyObject = cp.ContentTemplate.LoadContent();
+                       foreach (T childOfChild in FindVisualChildren<T>(dependencyObject))
+                       {
+                           yield return childOfChild;
+                       }
+                   }

                    foreach (T childOfChild in FindVisualChildren<T>(child))
                    {
                        yield return childOfChild;
                    }
                }
            }
        }

用例・使い方は以下の通りです。

        public Placement WidthPlacement
        {
            get
            {
                var detailPathGeometry = _detailPathGeometry;
                detailPathGeometry.ApplyTemplate();
                var widthCell = detailPathGeometry.FindVisualChildren<DockPanel>().First(x => x.Name == "WidthCell");
                                
                // コードが続きます...

                return Placement.Top;
            }
        }

私のコードの場合だと、ApplyTemplateメソッド実行が必須なようでした。FindVisualChildrenメソッドが不発に終わる場合はApplyTemplateメソッドの実行を検討してみてください。

0Like

Your answer might help someone💌