はじめに
WPFのxamlの基本といえばGridですが、基本的にはデザイン時にrow, columnを設定して使用します。
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="1"/> // Grid.Row, ColumnでTextBlockの位置を指定して表示させる
</Grid>
ただ、アプリを作っていると、たまにGridのRow, Columnを動的に設定したいというニーズがたまに出てくることがあります。
自分が開発していた時は、ListBoxのItemsPanelTemplateで動的にGridを設定したいということがありました。
カレンダーやスケジューラーのような月によって行数が変わるといった場合に、それをListBoxを使って実現しようと思うと、動的にGridを変更できるといいなということです。
以前 Drag&Dropの記事で、以下のような画面を紹介しましたが、この日にちごとのコマ割りで使用しています。
こんな感じでListBoxを作っています
見ていただけば、なんとなく構造がわかっていただけるのではと思います。
<!--CustomGridを使用して、ListBoxItemを動的配置しています-->
<ListBox Grid.Row="1"
Visibility="{Binding CalendarVisitility.Value}"
ItemsSource="{Binding CaseCalendarDatas}"
Style="{StaticResource MaterialDesignCardsListBox}"
ScrollViewer.CanContentScroll="False"
dd:DragDrop.IsDropTarget="True">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<customgrid:CustomGrid GridColumnCount="{Binding GridColumnCount.Value}"
ColumnDefinitionWidth="0"
GridRowCount="{Binding GridRowCount.Value}"
RowDefinitionHeight="0"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem"
BasedOn="{StaticResource MaterialDesignCardsListBoxItem}">
<Setter Property="Margin" Value="5"/>
<Setter Property="Grid.Row" Value="{Binding GridRowIndex}"/>
<Setter Property="Grid.Column" Value="{Binding GridColumnIndex}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<views:RegisterCaseControl DataContext="{Binding ControlViewModel}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
このxaml内でGridを継承したカスタムコントロール(CustomGrid)を使っています。
今回の記事は、このCustomGridのコードをご紹介させていただこうと思います。
カスタムGrid (AutoGrid)
Gridを継承して、AutoGridというカスタムコントロールを作成します。
public class AutoGrid : Grid
添付プロパティ
Columnの「幅」「数」
Rowの「高さ」「数」
にあたる添付プロパティ(Dependency Property)を作成します。
#region ColumnDefinitionWidth
/// <summary>
/// Row.Width
/// 負数:固定高(50pixel)
/// 0 :可変(1*)
/// 正数:指定高
/// </summary>
public static readonly DependencyProperty ColumnDefinitionWidthProperty =
DependencyProperty.Register("ColumnDefinitionWidth",
typeof(int),
typeof(AutoGrid),
new PropertyMetadata(50));
public int ColumnDefinitionWidth
{
get { return (int)GetValue(ColumnDefinitionWidthProperty); }
set { SetValue(ColumnDefinitionWidthProperty, value); }
}
#endregion
#region ColumnCount
public static readonly DependencyProperty GridColumnCountProperty =
DependencyProperty.RegisterAttached("GridColumnCount",
typeof(int),
typeof(AutoGrid),
new PropertyMetadata(0, new PropertyChangedCallback(OnGridColumnCountChanged)));
public int GridColumnCount
{
get { return (int)GetValue(GridColumnCountProperty); }
set { SetValue(GridColumnCountProperty, value); }
}
#endregion
#region RowDefinitionHeight
/// <summary>
/// Row.Height
/// 負数:固定高(14pixel)
/// 0 :可変(1*)
/// 正数:指定高
/// </summary>
public static readonly DependencyProperty RowDefinitionHeightProperty =
DependencyProperty.Register("RowDefinitionHeight",
typeof(int),
typeof(AutoGrid),
new PropertyMetadata(-1));
public int RowDefinitionHeight
{
get { return (int)GetValue(RowDefinitionHeightProperty); }
set { SetValue(RowDefinitionHeightProperty, value); }
}
#endregion
#region RowCount
public static readonly DependencyProperty GridRowCountProperty =
DependencyProperty.RegisterAttached("GridRowCount",
typeof(int),
typeof(AutoGrid),
new PropertyMetadata(1, new PropertyChangedCallback(OnGridRowCountChanged)));
public int GridRowCount
{
get { return (int)GetValue(GridRowCountProperty); }
set { SetValue(GridRowCountProperty, value); }
}
#endregion
PropertyChangedCallback
Row, Columnの数を設定した際のイベントを作成します。
OnGridRowCountChanged
Rowを指定された「数」で、指定された「高さ」で作成します
/// <summary>
/// GridRowCountで指定された数のRowDefinitionsを作成
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void OnGridRowCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is Grid) || (int)e.NewValue < 0)
return;
Grid grid = (Grid)d;
grid.RowDefinitions.Clear();
int heightProperty = (int)d.GetValue(RowDefinitionHeightProperty);
for (int i = 0; i < (int)e.NewValue; i++)
{
RowDefinition rowDefinition = new RowDefinition();
if (heightProperty < 0)
{
rowDefinition.Height = new GridLength(14, GridUnitType.Pixel);
}
else if (heightProperty > 0)
{
rowDefinition.Height = new GridLength(heightProperty, GridUnitType.Pixel);
}
else if (heightProperty == 0)
{
rowDefinition.Height = new GridLength(1, GridUnitType.Star);
}
grid.RowDefinitions.Add(rowDefinition);
}
}
OnGridColumnCountChanged
Columnを指定された「数」で、指定された「幅」で作成します
/// <summary>
/// GridColumnCountで指定された数のColumnDefinitionsを作成
/// </summary>
/// <param name="d"></param>
/// <param name="e"></param>
private static void OnGridColumnCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is Grid) || (int)e.NewValue < 0)
return;
Grid grid = (Grid)d;
grid.ColumnDefinitions.Clear();
int widthProperty = (int)d.GetValue(ColumnDefinitionWidthProperty);
for (int i = 0; i < (int)e.NewValue; i++)
{
ColumnDefinition columnDefinition = new ColumnDefinition();
if (widthProperty < 0)
{
columnDefinition.Width = new GridLength(50, GridUnitType.Pixel);
}
else if (widthProperty > 0)
{
columnDefinition.Width = new GridLength(widthProperty, GridUnitType.Pixel);
}
else if (widthProperty == 0)
{
columnDefinition.Width = new GridLength(1, GridUnitType.Star);
}
grid.ColumnDefinitions.Add(columnDefinition);
}
}
実装
あとは、作成したカスタムコントロールを参照指定して、xaml内で以下のような感じで記述すればOKです。
.ValueがついているのはReactivePropertyを使用しているためです。
xmlns:customgrid="clr-namespace:..(参照先)..."
<customgrid:CustomGrid GridColumnCount="{Binding GridColumnCount.Value}"
ColumnDefinitionWidth="0"
GridRowCount="{Binding GridRowCount.Value}"
RowDefinitionHeight="0"/>