質問者
ContentPresenterに複数の要素を直接入れる方法、もしくは代替案

質問
-
Windows 10向けに.NET Core 3.1とC#/WPFでデスクトップアプリを作っています。
アプリ内で使用頻度の多いXAMLコントロールを小分けにしたいと思っており、いまはContentControlとContentPresetnerで以下のような実装を進めています。
<ContentControl x:Class="Sample.PropertyItem" 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" mc:Ignorable="d" SizeChanged="OnSizeChanged" d:DesignHeight="450" d:DesignWidth="800"> <Grid Margin="5,0,5,5"> <Grid.ColumnDefinitions> <ColumnDefinition MinWidth="2cm" Width="*"/> <ColumnDefinition Width="auto"/> </Grid.ColumnDefinitions> <TextBlock x:Name="label" Grid.Column="0" Text="{Binding Text}" Padding="0,0,5,0" HorizontalAlignment="Left" VerticalAlignment="Center"/> <ContentPresenter x:Name="pi_cp" Grid.Column="1" MinWidth="50" VerticalAlignment="Center" Content="{Binding PropItem}"/> </Grid> </ContentControl>
[ContentProperty("PropItem")] public partial class PropertyItem : ContentControl { public PropertyItem() { InitializeComponent(); } public UIElement PropItem { get { return (UIElement)pi_cp.Content; } set { pi_cp.Content = value; } } }
これの応用として、以下のXAML文のようにパネルも共通化して少しでもコード量を少なくしたいのですが、私が調べた限りではItemsPresenterに該当するようなプロパティが見つからずにいます。XAMLではカスタムコントロールに対して複数の子要素を記述することはできないのでしょうか。
<l:StackedList> <l:PropertyItem Text="1"> <TextBox/> </l:PropertyItem> <l:PropertyItem Text="2"> <ComboBox/> </l:PropertyItem> <l:PropertyItem Text="3"> <CheckBox/> </l:PropertyItem> </l:StackedList>
<!-- StackedList -->
<ItemsControl x:Class="Sample.ListBase" 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" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <DockPanel> <Border x:Name="border" Stroke="Black"> </Border> <ScrollViewer x:Name="sv" DockPanel.Dock="Right"> <StackPanel Orientation="Vertical"> <ItemsPresenter x:Name="items" /> </StackPanel> </ScrollViewer> </DockPanel> </ItemsControl>
ちなみに、ControlTemplateを使う方法も試そうとはしたのですが、テンプレートの要素のみを抜き取ったようなざっくりとした例しか見つからず、プログラムコードにうまく組み込めておりません。
- 編集済み Tank2005 2020年2月22日 12:32
すべての返信
-
こんな?
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sample="clr-namespace:Sample" Title="MainWindow" Height="200" Width="300"> <Window.Resources> <Style TargetType="{x:Type ContentControl}" x:Key="labeledItem"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type ContentControl}"> <Grid Margin="5,0,5,5" > <Grid.ColumnDefinitions> <ColumnDefinition MinWidth="2cm" Width="*"/> <ColumnDefinition Width="auto"/> </Grid.ColumnDefinitions> <TextBlock x:Name="label" Grid.Column="0" Text="{TemplateBinding Tag}" Padding="0,0,5,0" HorizontalAlignment="Left" VerticalAlignment="Center"/> <ContentPresenter x:Name="pi_cp" Grid.Column="1" MinWidth="50" VerticalAlignment="Center"/> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> <DataTemplate x:Key="sampleItemTemplate"> <Grid Margin="5,0,5,5" > <Grid.ColumnDefinitions> <ColumnDefinition MinWidth="2cm" Width="*"/> <ColumnDefinition Width="auto"/> </Grid.ColumnDefinitions> <TextBlock x:Name="label" Grid.Column="0" Text="{Binding Tag}" Padding="0,0,5,0" HorizontalAlignment="Left" VerticalAlignment="Center"/> <ContentPresenter x:Name="pi_cp" MinWidth="50" Grid.Column="1" VerticalAlignment="Center" Content="{Binding}" /> </Grid> </DataTemplate> <Style x:Key="testListBoxStyle" TargetType="{x:Type ListBox}"> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" /> <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Disabled" /> <Setter Property="BorderThickness" Value="0" /> <Setter Property="ItemContainerStyle"> <Setter.Value> <Style TargetType="{x:Type ListBoxItem}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate> <Grid Margin="5,0,5,5" > <Grid.ColumnDefinitions> <ColumnDefinition MinWidth="2cm" Width="*"/> <ColumnDefinition Width="auto"/> </Grid.ColumnDefinitions> <TextBlock x:Name="label" Grid.Column="0" Text="{Binding Tag}" Padding="0,0,5,0" HorizontalAlignment="Left" VerticalAlignment="Center"/> <ContentPresenter x:Name="pi_cp" MinWidth="50" Grid.Column="1" VerticalAlignment="Center" Content="{Binding}" /> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </Setter.Value> </Setter> </Style> </Window.Resources> <!-- ******************* --> <UniformGrid Columns="2" Rows="2"> <GroupBox Header="A"> <StackPanel> <ContentControl Tag="1" Style="{StaticResource labeledItem}"> <TextBlock Text="TextBlock"/> </ContentControl> <ContentControl Tag="2" Style="{StaticResource labeledItem}"> <ComboBox /> </ContentControl> <ContentControl Tag="3" Style="{StaticResource labeledItem}"> <CheckBox /> </ContentControl> </StackPanel> </GroupBox> <GroupBox Header="B"> <ListBox Style="{StaticResource testListBoxStyle}" > <TextBox Tag="1"/> <ComboBox Tag="2"/> <CheckBox Tag="3"/> </ListBox> </GroupBox> <GroupBox Header="C"> <sample:PropertyItem ItemTemplate="{StaticResource sampleItemTemplate}"> <TextBox Tag="1"/> <ComboBox Tag="2"/> <CheckBox Tag="3"/> </sample:PropertyItem> </GroupBox> </UniformGrid> </Window>
namespace Sample { using System; using System.Collections.Generic; using System.Windows; using System.Windows.Controls; using System.Windows.Media; [System.Windows.Markup.ContentPropertyAttribute("Children")] class PropertyItem : Control { public PropertyItem() { this.Children = new System.Collections.ObjectModel.ObservableCollection<object>(); Children.CollectionChanged += Children_CollectionChanged; this.VerticalAlignment= VerticalAlignment.Top; this.HorizontalAlignment= HorizontalAlignment.Stretch; } private void Children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { foreach (ContentPresenter cp in _ChildElements) { this.RemoveVisualChild(cp); } this._ChildElements.Clear(); foreach (object o in this.Children) { ContentPresenter cp = new ContentPresenter(); cp.HorizontalAlignment= HorizontalAlignment.Stretch; cp.VerticalAlignment= VerticalAlignment.Stretch; cp.ContentTemplate = this.ItemTemplate; cp.Content = o; this._ChildElements.Add(cp); this.AddVisualChild(cp); } this.InvalidateMeasure(); } public System.Collections.ObjectModel.ObservableCollection<object> Children { get; } private List<ContentPresenter> _ChildElements = new List<ContentPresenter>(); protected override int VisualChildrenCount=> _ChildElements.Count; protected override Visual GetVisualChild(int index)=> _ChildElements[index]; public DataTemplate ItemTemplate { get; set; } protected override Size MeasureOverride(Size constraint) { double y = 0; double h = constraint.Height; double w = constraint.Width; foreach (ContentPresenter cp in _ChildElements) { cp.Measure(new Size(w, h)); y += cp.DesiredSize.Height; h -= cp.DesiredSize.Height; } return new Size(w, y); } protected override Size ArrangeOverride(Size arrangeBounds) { double y = 0; foreach (ContentPresenter cp in _ChildElements) { Rect r = new Rect(new Point(0, y), cp.DesiredSize); cp.Arrange(r); y += cp.DesiredSize.Height; } return new Size(arrangeBounds.Width, Math.Min(y, arrangeBounds.Height)); } } }
個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)
- 回答の候補に設定 kumo-msftMicrosoft contingent staff, Moderator 2020年2月28日 5:42
-
Tank2005さん、こんにちは。フォーラムオペレーターのクモです。
MSDNフォーラムにご投稿くださいましてありがとうございます。
ご質問いただいた件ですが、その後いかがでしょうか。
gekkaさんから寄せられた投稿はお役に立ちましたか。
参考になった投稿には [回答としてマーク] をお願い致します。
設定いただくことで、
他のユーザーもお役に立つ回答を見つけやすくなります。
お手数ですが、ご協力の程どうかよろしくお願いいたします。MSDN/ TechNet Community Support Kumo ~参考になった投稿には「回答としてマーク」をご設定ください。なかった場合は「回答としてマークされていない」も設定できます。同じ問題で後から参照した方が、情報を見つけやすくなりますので、 ご協力くださいますようお願いいたします。また、MSDNサポートに賛辞や苦情がある場合は、MSDNFSF@microsoft.comまでお気軽にお問い合わせください。~