かな~り昔に、コンボボックスのドロップダウンがリサイズ可能なコントロールをUserControlを使って作った。
との時のエントリはこちら。
あまりにも手抜きチックだったので、ちょびっと時間をとってそれっぽいのを作ってみた。
ただ、それでも本物のComboBoxに比べると見た目が微妙に劣ってる。細かいところまで合わせようとすると非常に骨が折れるので今回のも妥協の塊であることを最初に断っておきますorz
コントロールの名前は、MyComboBoxにしてComboBoxを継承して作ってる。
追加したプロパティは、ドロップダウンの幅と高さのためのプロパティを2つ。後は、ActualWidthが変更されたときのドロップダウンの幅も変更されるような仕組みと、Thumbのドラッグイベントに応答してドロップダウンの幅と高さを変えるコードを仕込んだ。
後はXAML側でPopupの幅と高さをドロップダウンの幅と高さのプロパティにバインドしてやればOKという寸法です。
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
namespace CustomComboBox
{
/// <summary>
/// ドロップダウンのサイズをマウスで変更可能なコンボボックス
/// </summary>
public class MyComboBox : ComboBox
{
#region DropDownの幅
public double DropDownWidth
{
get { return (double)GetValue(DropDownWidthProperty); }
set { SetValue(DropDownWidthProperty, value); }
}
// Using a DependencyProperty as the backing store for DropDownWidth. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DropDownWidthProperty =
DependencyProperty.Register("DropDownWidth",
typeof(double),
typeof(MyComboBox),
new UIPropertyMetadata(0.0,
(sender, e) => { },
(sender, value) =>
{
// ドロップダウンの幅はActualWidthより小さくしないようにしてみた
double v = (double)value;
double actualWidth = (double)sender.GetValue(FrameworkElement.ActualWidthProperty);
return Math.Max(actualWidth, v);
}));
#endregion
#region DropDownの高さ(悔しいけどデフォルト150で固定)
public double DropDownHeight
{
get { return (double)GetValue(DropDownHeightProperty); }
set { SetValue(DropDownHeightProperty, value); }
}
// Using a DependencyProperty as the backing store for DropDownHeight. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DropDownHeightProperty =
DependencyProperty.Register("DropDownHeight",
typeof(double),
typeof(MyComboBox),
new UIPropertyMetadata(150.0,
(sender, e) => { },
(sender, e) =>
{
// 高さは16以下にならないようにしてみた
double value = (double)e;
return Math.Max(value, 16.0);
}));
#endregion
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == FrameworkElement.ActualWidthProperty)
{
// ActualWidthが変更されたら、DropDownWidthのプロパティの値も変更する。
this.CoerceValue(DropDownWidthProperty);
}
}
static MyComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyComboBox), new FrameworkPropertyMetadata(typeof(MyComboBox)));
}
public MyComboBox()
{
// ドラッグのイベントを登録
AddHandler(Thumb.DragDeltaEvent, new DragDeltaEventHandler(Thumb_DragDelta));
}
private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
// ドラッグで移動したぶんだけ、ドロップダウンのサイズを変更する
DropDownWidth += e.HorizontalChange;
DropDownHeight += e.VerticalChange;
}
}
}
そして、Generic.xamlにComboBoxのテンプレートの劣化版みたいなものをゴリゴリと書いていった。
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomComboBox">
<Style TargetType="{x:Type local:MyComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyComboBox}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel>
<Grid Name="editorHolder">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Name="ContentHolder"
Grid.Row="0" Grid.Column="0"
VerticalAlignment="Center"
Content="{TemplateBinding SelectedValue}"
ContentTemplate="{TemplateBinding ItemTemplate}"
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"/>
<TextBox Name="ContentEditor"
Grid.Row="0" Grid.Column="0"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SelectedValue}"
Visibility="Hidden"/>
<ToggleButton Grid.Row="0" Grid.Column="1"
ClickMode="Press"
IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsDropDownOpen}">
<Polygon Points="0,0 6,0 3,6" Fill="Black"
Margin="5"/>
</ToggleButton>
</Grid>
<Popup IsOpen="{TemplateBinding IsDropDownOpen}"
Width="{TemplateBinding DropDownWidth}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DropDownHeight}">
<Border Background="White"
BorderBrush="Black"
BorderThickness="1">
<DockPanel>
<Thumb DockPanel.Dock="Bottom"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="15"
Height="15" />
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<StackPanel IsItemsHost="True" />
</ScrollViewer>
</DockPanel>
</Border>
</Popup>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEditable" Value="True">
<Setter TargetName="ContentHolder" Property="Visibility" Value="Hidden" />
<Setter TargetName="ContentEditor" Property="Visibility" Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
このBlog的に珍しいのは、Triggerを使ってIsEditableの値に応じて表示するコントロールを切り替えてるのくらいかな?後は単純に物を置いていってます。
動作
出来上がったMyComboBoxをWindowにおいてみた。Window1.xamlは↓のような感じ。いつもどおりのPersonクラスも定義してある。
<Window x:Class="CustomComboBox.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomComboBox"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<x:Array x:Key="people" Type="{x:Type local:Person}">
<local:Person Name="田中 太郎" />
<local:Person Name="田中 次郎" />
<local:Person Name="田中 三郎" />
<local:Person Name="田中 四郎" />
<local:Person Name="田中 五郎" />
<local:Person Name="田中 六郎" />
<local:Person Name="田中 七郎" />
<local:Person Name="田中 八郎" />
<local:Person Name="田中 九郎" />
<local:Person Name="田中 十郎" />
<local:Person Name="じゅげむじゅげむ ごこうのすりきれ かいじゃりすいぎょのすいぎょうまつうんらいまつふうらいまつ くうねるところにすむところやぶらこうじのぶらこうじ ぱいぽぱいぽぱいぽのしゅーりんがん しゅーりんがんのぐーりんだい ぐーりんだいのぽんぽこぴーのぽんぽこなーのちょうきゅうめいのちょうすけ" />
</x:Array>
<DataTemplate x:Key="personDataTemplate" DataType="{x:Type local:Person}">
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</Window.Resources>
<StackPanel>
<local:MyComboBox ItemsSource="{StaticResource people}"
ItemTemplate="{StaticResource personDataTemplate}"/>
</StackPanel>
</Window>
これを実行してドロップダウンを表示させてみると最初は↓のような感じで出てくる。
右下のThumbをつまんでドラッグするとサイズ変更もばっちりできてる。
因みに、うちのディスプレイでは、最大までドロップダウンの幅を広げても「じゅげむじゅげむ…ちょうすけ」さんの名前は表示しきれませんでした。
このプロジェクトの全体のダウンロードはこちらから。