開発環境:VS2013 Pro
.NET Framework 4.5
カスタムコントロールに double と IEnumerable<double> の
依存関係プロパティを追加しました。
さらに IEnumerable の依存関係プロパティを追加し、
自身の配列を指定することで階層構造を持つカスタムコントロールとなるようにしました。
このような状況で、
親となるカスタムコントロールに対するプロパティは
通常通りにデータバインドできました。
しかし、
その下の子供となるカスタムコントロールに対する
double あるいは IEnumerable<double> の依存関係プロパティに対して
データバインドしようとしましたが、
プロパティ変更通知が機能しませんでした。
以下に動作確認用のコードを掲載します。
もしかして XAML の中で <x:Array ・・・> を使用しているのがいけないような気がしていますが、
他のコードで同じことを実現する方法が思い付きませんでした。
どのようにすれば子供のカスタムコントロールの
プロパティ変更通知が機能するのでしょうか。
動作確認に用いたプロジェクトの構造は下図のようになります。
デフォルトの WPF アプリケーションプロジェクトを作成し、
WPF カスタムコントロールをひとつと、
MainViewModel.cs を追加しただけのプロジェクトです。
![テスト用ソリューションツリー構造]()
以下動作確認用に編集した部分のコードです。
カスタムコントロールのコード
namespace Test
{
using System.Collections;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
public class CustomControl1 : Control
{
#region Items プロパティ
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(IEnumerable), typeof(CustomControl1), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (s, e) => { (s as CustomControl1).OnItemsChanged(); }));
public IEnumerable Items
{
get { return (IEnumerable)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
private void OnItemsChanged()
{
var str = "NULL";
if (Items != null)
{
str = "[ ";
foreach (var obj in Items)
{
var item = obj as Control;
str += item != null ? item.Name + " " : "NULL ";
}
str += "]";
}
System.Diagnostics.Debug.WriteLine("Items changed. " + str);
}
#endregion Items プロパティ
#region Values プロパティ
public static readonly DependencyProperty ValuesProperty = DependencyProperty.Register("Values", typeof(IEnumerable<double>), typeof(CustomControl1), new FrameworkPropertyMetadata(new double[] { 1.0, 2.0, 3.0 }, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (s, e) => { (s as CustomControl1).OnValuesChanged(); }));
public IEnumerable<double> Values
{
get { return (IEnumerable<double>)GetValue(ValuesProperty); }
set { SetValue(ValuesProperty, value); }
}
private void OnValuesChanged()
{
string valuesString = "NULL";
if (Values != null)
{
valuesString = "[ ";
foreach (var value in Values)
{
valuesString += value.ToString() + " ";
}
valuesString += "]";
}
System.Diagnostics.Debug.WriteLine("[" + Name + "] ValuesProperty changed. " + valuesString);
}
#endregion Values プロパティ
#region Test プロパティ
public static readonly DependencyProperty TestProperty = DependencyProperty.Register("Test", typeof(double), typeof(CustomControl1), new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (s, e) => { (s as CustomControl1).OnTestChanged(); }));
public double Test
{
get { return (double)GetValue(TestProperty); }
set { SetValue(TestProperty, value); }
}
private void OnTestChanged()
{
System.Diagnostics.Debug.WriteLine("[" + Name + "] Test changed to " + Test.ToString());
}
#endregion Test プロパティ
#region コンストラクタ
public CustomControl1()
{
Values = new double[] { 10.0, 20.0, 30.0 };
}
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
#endregion コンストラクタ
}
}
MainWindow.xaml のコード
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Test"
Title="MainWindow" Height="350" Width="525"><Window.DataContext><local:MainViewModel /></Window.DataContext><Grid><StackPanel><Button Content="click me." Command="{Binding TestCommand}" /><local:CustomControl1 x:Name="parent" Values="{Binding ParentValues}" Test="{Binding ParentTestValue}"><local:CustomControl1.Items><x:Array Type="{x:Type local:CustomControl1}"><local:CustomControl1 x:Name="child01" Values="{Binding SampleValues1}" Test="{Binding TestValue1}" /><local:CustomControl1 x:Name="child02" Values="{Binding SampleValues2}" Test="{Binding TestValue2}" /><local:CustomControl1 x:Name="child03" Values="{Binding SampleValues3}" Test="{Binding TestValue3}" /></x:Array></local:CustomControl1.Items></local:CustomControl1></StackPanel></Grid></Window>
MainViewModel のコード
namespace Test
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Input;
public class MainViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged のメンバ
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void RaisePropertyChanged(string propertyName)
{
var h = PropertyChanged;
if (h != null)
h(this, new PropertyChangedEventArgs(propertyName));
}
#endregion INotifyPropertyChanged のメンバ
private IEnumerable<double> parentValues;
public IEnumerable<double> ParentValues
{
get { return parentValues; }
set
{
parentValues = value;
RaisePropertyChanged("ParentValues");
}
}
private IEnumerable<double> sampleValues1;
public IEnumerable<double> SampleValues1
{
get { return sampleValues1; }
set
{
sampleValues1 = value;
RaisePropertyChanged("SampleValues1");
}
}
private IEnumerable<double> sampleValues2;
public IEnumerable<double> SampleValues2
{
get { return sampleValues2; }
set
{
sampleValues2 = value;
RaisePropertyChanged("SampleValues2");
}
}
private IEnumerable<double> sampleValues3;
public IEnumerable<double> SampleValues3
{
get { return sampleValues3; }
set
{
sampleValues3 = value;
RaisePropertyChanged("SampleValues3");
}
}
private double parentTestValue;
public double ParentTestValue
{
get { return parentTestValue; }
set
{
parentTestValue = value;
RaisePropertyChanged("ParentTestValue");
}
}
private double testValue1;
public double TestValue1
{
get { return testValue1; }
set
{
testValue1 = value;
RaisePropertyChanged("TestValue1");
}
}
private double testValue2;
public double TestValue2
{
get { return testValue2; }
set
{
testValue2 = value;
RaisePropertyChanged("TestValue2");
}
}
private double testValue3;
public double TestValue3
{
get { return testValue3; }
set
{
testValue3 = value;
RaisePropertyChanged("TestValue3");
}
}
public MainViewModel()
{
ParentValues = new double[] { 111.0, 222.0, 333.0 };
SampleValues1 = new double[] { 100.0, 200.0, 300.0 };
SampleValues2 = new double[] { 400.0, 500.0, 600.0 };
SampleValues3 = new double[] { 700.0, 800.0, 900.0 };
ParentTestValue = 111.0;
TestValue1 = 100.0;
TestValue2 = 200.0;
TestValue3 = 300.0;
}
private DelegateCommand testCommand;
public DelegateCommand TestCommand
{
get
{
if (testCommand == null)
testCommand = new DelegateCommand(_ =>
{
System.Diagnostics.Debug.WriteLine("TestCommand executed.");
ParentValues = new double[] { 1111.0, 2222.0, 3333.0 };
SampleValues1 = new double[] { 1000.0, 2000.0, 3000.0 };
SampleValues2 = new double[] { 4000.0, 5000.0, 6000.0 };
SampleValues3 = new double[] { 7000.0, 8000.0, 9000.0 };
ParentTestValue = 1111.0;
TestValue1 = 1000.0;
TestValue2 = 2000.0;
TestValue3 = 3000.0;
});
return testCommand;
}
}
}
public class DelegateCommand : ICommand
{
private Action<object> _execute;
private Func<object, bool> _canExecute;
public DelegateCommand(Action<object> execute)
: this(execute, null)
{
}
public DelegateCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
#region ICommand のメンバ
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event System.EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested+= value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
if (_execute != null)
_execute(parameter);
}
#endregion ICommand のメンバ
}
}
余談になりますが、
TabControl なんかでは <x:Array /> を使用せずに
次のように記述できますよね。
これって ItemsControl を使わずに実現するには
どうればいいんでしょうか。
これができれば解決できる気もするんですが…。
<TabControl><TabItem Header="1" /><TabItem Header="2" /><TabItem Header="3" /></TabControl>