2

私がやっていることを要約すると、チェックされたリストボックスのように見え、2 つの依存関係プロパティを持つカスタム コントロールがあります。

前述したように、カスタム コントロールは 2 つの異なる DependencyProperties を公開します。1 つは Options というオプションのリストで、もう 1 つのSelectedOptionsというプロパティは、[Flags] 属性を使用して値の組み合わせを設定できる特定の Enum 型です。私の UserControl には、チェックボックスとともにオプションを表示するために使用される ListBox に似た ItemsControl が含まれています。チェック ボックスがオンまたはオフの場合、SelectedOptions プロパティは、対応するビット演算を使用して適宜更新する必要があります。

私が経験している問題は、プライベート フィールドを維持し、プロパティ変更イベントを処理してプロパティを更新する以外に方法がないことです。これは、WPF では不自然に感じます。ValueConverters を使用してみましたが、実際には値コンバーター バインディングでバインディングを使用できないという問題が発生したため、許容できない ValueConverter パラメーターとして列挙値をハードコーディングする必要があります。誰かがこれを正気で行う方法の良い例を見たことがあれば、どんな意見でも大歓迎です。

補足: これは、依存関係プロパティが計算値または遅延値をどのように許可しないかについて頭を悩ませようとしていたときに、過去にも発生した問題でした。もう 1 つの例は、子コントロールのプロパティを親コントロールのプロパティとして公開したい場合です。この場合、バインディングを使用することをお勧めしますが、ターゲットが親プロパティになるようにバインディングを配置するため、子コントロールのプロパティが依存関係プロパティである場合にのみ機能します。親コントロールのユーザーが独自のバインディングを設定したい場合は上書きされます。そのプロパティのために。

4

3 に答える 3

2

コードを詳しく調べないと、あなたが何をしようとしているのか正確にはわかりませんが、私はあなたのシナリオについて漠然とした考えを持っていると思います。私はあなたのために例を作成し、これに似たものを示しています。Windowデモンストレーションを容易にするために、新しいコントロールを作成するのではなく、すべてのコードを1つにまとめました。手始めに、ウィンドウのXAMLを見てみましょう。

<Window x:Class="TestWpfApplication.DataBoundFlags"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWpfApplication"
Title="DataBoundFlags" Height="300" Width="300"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>

    <ListBox ItemsSource="{Binding AvailableOptions}" Grid.Row="0">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <CheckBox Content="{Binding}" CommandParameter="{Binding}"
                          Command="{Binding RelativeSource={RelativeSource FindAncestor, 
                          AncestorType={x:Type Window}}, Path=SelectCommand}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

    <TextBlock Text="{Binding SelectedOptions}" Grid.Row="1"/>
</Grid>

ウィンドウDataContextは独自の分離コードに設定されているので、そこでプロパティにバインドできます。私はいくつかのプロパティを持ってAvailableOptionsいます-あなたが選ぶことができるすべてのオプションです。SelectedOptionsユーザーが現在選択しているオプションです。SelectCommandRelayCommand、フラグを追加SelectedOptionsまたは削除するために使用されます。

XAMLの残りの部分は非常に単純である必要があります。はListBox使用可能なすべてのオプションにバインドされ、各オプションは単一のとして表されますCheckBoxCommandParameterオプションアイテム自体にバインドされている、に注意してください。それでは、魔法が発生するコードビハインドを見てみましょう。

[Flags()]
public enum Options
{
    Plain = 0,
    Ketchup = 1,
    Mustard = 2,
    Mayo = 4,
    HotSauce = 8
}

public partial class DataBoundFlags : Window
{
    public static readonly DependencyProperty SelectedOptionsProperty =
        DependencyProperty.Register("SelectedOptions", typeof(Options), typeof(DataBoundFlags));

    public Options SelectedOptions
    {
        get { return (Options)GetValue(SelectedOptionsProperty); }
        set { SetValue(SelectedOptionsProperty, value); }
    }

    public List<Options> AvailableOptions
    {
        get
        {
            return new List<Options>()
            {
                Options.Ketchup,
                Options.Mustard,
                Options.Mayo,
                Options.HotSauce
            };
        }
    }

    public ICommand SelectCommand
    {
        get;
        private set;
    }

    /// <summary>
    /// If the option is selected, unselect it.
    /// Otherwise, select it.
    /// </summary>
    private void OnSelect(Options option)
    {
        if ((SelectedOptions & option) == option)
            SelectedOptions = SelectedOptions & ~option;
        else
            SelectedOptions |= option;
    }

    public DataBoundFlags()
    {
        SelectCommand = new RelayCommand((o) => OnSelect((Options)o));
        InitializeComponent();
    }
}

上から始めて、列挙型宣言、SelectedOptions依存関係プロパティ、およびAvailableOptionsプロパティ(変更されないため、標準のCLRプロパティにすることができます)が続きます。次に、コマンドと、コマンドに対して実行されるハンドラー(オプションがオンまたはオフの場合)があります。まず、コマンドがどのように接続されているかに注目してください。新しいコマンドを作成しRelayCommandて実行するようにOnSelect指示し、コマンドパラメータを渡します。これは、XAMLでバインドされたのと同じコマンドパラメーターであることに注意してください。つまり、現在チェックされているオプションまたはチェックされていないオプションです。SelectedOptionsそのオプションをビット演算子の使用と比較します。オプションが存在する場合、それはチェックを外していることを意味し、ビットごとのANDを使用してそれをクリアする必要があります。存在しない場合は、ビットごとのORを使用して選択済みに追加します。

その場合、SelectedOptions依存関係プロパティが自動的に更新さTextBlockれ、XAMLのバインディングが更新されます。最終結果は次のとおりです。

代替テキスト

于 2010-04-13T20:47:33.257 に答える
1

別の解決策

この状況では、はるかにクリーンだと思う非常に異なるソリューションを使用します。私が作成したいくつかのユーティリティ クラスを使用すると、コマンドやコレクションの更新などを処理するアプリケーション コードを記述することなく、SelectedOptions を直接バインドできます。

EnumExpansion クラス

次のシグネチャを持つ単純なクラスを作成しました。

public class EnumExpansion : DependencyObject, IList, INotifyCollectionChanged
{
  public object EnumValue   { ... // DependencyProperty
  ... // IList & INotifyCollectionChanged implementation
}

EnumValue は、任意の列挙型に設定できます。EnumValue が設定されている場合、現在の EnumValue にないすべてのフラグを削除し、現在の EnumValue にすべてのフラグを追加することによって、内部の ObservableCollection が更新されます。内部コレクションが変更されるたびに、EnumValue が更新されます。

BindableSelectedItems プロパティ

また、ListBox がその SelectedItems プロパティをバインドできるようにする単純な添付プロパティも作成しました。次のように使用されます。

<ListBox ItemsSource="{Binding Options}"
  edf:ListBoxHelper.BindableSelectedItems="{Binding SelectedOptionsExpansion}" />

添付プロパティは、ListBox で SelectionChanged をサブスクライブし、プロパティ値 (INotifyCollectionChanged 型) で CollectionChanged をサブスクライブすることによって実装されます。

SelectedOptionsExpansion の初期化

これは XAML で行うことができますが、コードでは非常に簡単です。

public EnumExpansion SelectedOptionsExpansion { get; set; }

  ...
  SelectedOptionsExpansion = new EnumExpansion();
  BindingOperations.SetBinding(SelectedOptionsExpansion, EnumExpansion.EnumValueProperty,
    new Binding { Path = "SelectedOptions", Source = this });
  ...

使い方

リストボックスへの列挙:

  1. SelectedOptions の変更 (コードまたはデータ バインディングによる)
  2. SelectedOptionsExpansion の EnumValue プロパティがバインディングによって更新され、EnumExpansion のコレクションが変更されます。
  3. CollectionChange イベントは、ListBox 内の選択を更新する ListBoxHelper コードによって取得されます。

リストボックスから列挙型へ:

  1. リストボックスでアイテムが選択または選択解除された
  2. ListBoxHelper はそれを取得して EnumExpansion コレクションを更新します。これにより、EnumValue プロパティが更新されます。
  3. EnumValue は BindsTwoWayByDefault であるため、SelectedOptions 値が更新されます。

このソリューションを好む理由

2 つのユーティリティ クラスが作成されると、バインディング プロセスの残りは簡単なデータ バインディングです。アプリケーション コードでコマンドを処理したりコレクションを更新したりする必要はありません。これらはすべてユーティリティ クラス内に隠されています。

于 2010-04-14T21:48:47.120 に答える
0

デフォルトの概念をサポートするには、プロパティにバインドを設定する必要がありCheckBox.IsCheckedます。DataContext現在のオプション (関連するチェックボックスにある) とSelectedOptions、ウィンドウにあるプロパティの両方が必要です。したがって、このバインディングは次のようになります。

<CheckBox.IsChecked>
    <MultiBinding Converter="{StaticResource FlagsToBoolConverter}">
        <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type Window}}" 
                 Path="SelectedOptions"/>
        <Binding RelativeSource="{RelativeSource Self}" Path="DataContext"/>
    </MultiBinding>
</CheckBox.IsChecked>

FlagsToBoolConverter単にこれらを取り込んで、現在のオプションが にあるかどうかを確認しますSelectedOptions

public class FlagsToBoolConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        Options selected = (Options)values[0];
        Options current = (Options)values[1];

        return ((selected & current) == current);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return null;
    }
}

SelectedOptionsコンストラクターで をデフォルト値に設定してみてください。関連するCheckBoxものが自動的にチェックされ、すべてのバインディングが引き続き機能することに注意してください。勝利!

于 2010-04-14T15:34:30.823 に答える