2008-11-28 7 views
13

:Bayrak numaralandırmalarının bir bitine iki yönlü bir onay kutusunu nasıl bağlayabilirsiniz? İyi bir WPF bağlama meydan isteyenler için

Ben bayrakları numaralandırma (teşekkürler Ian Oakes, original MSDN post) bağımsız bir bit için bir onay kutusu bağlama iki yönlü bir neredeyse işlevsel bir örnek var. Sorun ise, bağlamanın tek yönlü olduğu gibi davranmasıdır (DataContext için UI, tersi değil). Bu nedenle etkin bir şekilde onay kutusu başlatılmaz, ancak eğer değiştirilirse veri kaynağı doğru şekilde güncellenir. Ekli, bit tabanlı bağlamayı etkinleştirmek için bağlı bazı bağımlılık özelliklerini tanımlayan sınıftır. Fark ettiğim şey, DataContext'i değiştirmeye zorladığımda bile ValueChanged'in hiçbir zaman çağrılmadığıdır. Dayak, Açıkça Cilt Mod = TwoWay ayarı DataContext güncellemelerini, herhangi bir makul FrameworkMetadataPropertyOptions (AffectsRender, BindsTwoWayByDefault), dışarı kaynamaktadır onaylamak için bir etiket ve metin kutusu kullanarak, mülkiyet tanımları sırasını değiştirme: denedim ne

duvardaki kafa, anlaşmazlık durumunda ValueProperty'yi EnumValueProperty olarak değiştirin.

Önerebileceğiniz herhangi bir şey için herhangi bir öneri veya fikir son derece takdir edilecektir!

numaralandırma:


    [Flags] 
    public enum Department : byte 
    { 
     None = 0x00, 
     A = 0x01, 
     B = 0x02, 
     C = 0x04, 
     D = 0x08 
    } // end enum Department 

XAML kullanımı:


    CheckBox Name="studentIsInDeptACheckBox" 
      ctrl:CheckBoxFlagsBehaviour.Mask="{x:Static c:Department.A}" 
      ctrl:CheckBoxFlagsBehaviour.IsChecked="{Binding Path=IsChecked, RelativeSource={RelativeSource Self}}" 
      ctrl:CheckBoxFlagsBehaviour.Value="{Binding Department}" 

sınıfı:


    /// 
    /// A helper class for providing bit-wise binding. 
    /// 
    public class CheckBoxFlagsBehaviour 
    { 
     private static bool isValueChanging; 

     public static Enum GetMask(DependencyObject obj) 
     { 
      return (Enum)obj.GetValue(MaskProperty); 
     } // end GetMask 

     public static void SetMask(DependencyObject obj, Enum value) 
     { 
      obj.SetValue(MaskProperty, value); 
     } // end SetMask 

     public static readonly DependencyProperty MaskProperty = 
      DependencyProperty.RegisterAttached("Mask", typeof(Enum), 
      typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(null)); 

     public static Enum GetValue(DependencyObject obj) 
     { 
      return (Enum)obj.GetValue(ValueProperty); 
     } // end GetValue 

     public static void SetValue(DependencyObject obj, Enum value) 
     { 
      obj.SetValue(ValueProperty, value); 
     } // end SetValue 

     public static readonly DependencyProperty ValueProperty = 
      DependencyProperty.RegisterAttached("Value", typeof(Enum), 
      typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(null, ValueChanged)); 

     private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
     { 
      isValueChanging = true; 
      byte mask = Convert.ToByte(GetMask(d)); 
      byte value = Convert.ToByte(e.NewValue); 

      BindingExpression exp = BindingOperations.GetBindingExpression(d, IsCheckedProperty); 
      object dataItem = GetUnderlyingDataItem(exp.DataItem); 
      PropertyInfo pi = dataItem.GetType().GetProperty(exp.ParentBinding.Path.Path); 
      pi.SetValue(dataItem, (value & mask) != 0, null); 

      ((CheckBox)d).IsChecked = (value & mask) != 0; 
      isValueChanging = false; 
     } // end ValueChanged 

     public static bool? GetIsChecked(DependencyObject obj) 
     { 
      return (bool?)obj.GetValue(IsCheckedProperty); 
     } // end GetIsChecked 

     public static void SetIsChecked(DependencyObject obj, bool? value) 
     { 
      obj.SetValue(IsCheckedProperty, value); 
     } // end SetIsChecked 

     public static readonly DependencyProperty IsCheckedProperty = 
      DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), 
      typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(false, IsCheckedChanged)); 

     private static void IsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
     { 
      if (isValueChanging) return; 

      bool? isChecked = (bool?)e.NewValue; 
      if (isChecked != null) 
      { 
       BindingExpression exp = BindingOperations.GetBindingExpression(d, ValueProperty); 
       object dataItem = GetUnderlyingDataItem(exp.DataItem); 
       PropertyInfo pi = dataItem.GetType().GetProperty(exp.ParentBinding.Path.Path); 

       byte mask = Convert.ToByte(GetMask(d)); 
       byte value = Convert.ToByte(pi.GetValue(dataItem, null)); 

       if (isChecked.Value) 
       { 
        if ((value & mask) == 0) 
        { 
         value = (byte)(value + mask); 
        } 
       } 
       else 
       { 
        if ((value & mask) != 0) 
        { 
         value = (byte)(value - mask); 
        } 
       } 

       pi.SetValue(dataItem, value, null); 
      } 
     } // end IsCheckedChanged 

     /// 
     /// Gets the underlying data item from an object. 
     /// 
     /// The object to examine. 
     /// The underlying data item if appropriate, or the object passed in. 
     private static object GetUnderlyingDataItem(object o) 
     { 
      return o is DataRowView ? ((DataRowView)o).Row : o; 
     } // end GetUnderlyingDataItem 
    } // end class CheckBoxFlagsBehaviour 

cevap

37

Bir değer dönüştürücü kullanabilirsiniz.

[Flags] 
public enum Department 
{ 
    None = 0, 
    A = 1, 
    B = 2, 
    C = 4, 
    D = 8 
} 

public partial class Window1 : Window 
{ 
    public Window1() 
    { 
     InitializeComponent(); 

     this.DepartmentsPanel.DataContext = new DataObject 
     { 
      Department = Department.A | Department.C 
     }; 
    } 
} 

public class DataObject 
{ 
    public DataObject() 
    { 
    } 

    public Department Department { get; set; } 
} 

public class DepartmentValueConverter : IValueConverter 
{ 
    private Department target; 

    public DepartmentValueConverter() 
    { 
    } 

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     Department mask = (Department)parameter; 
     this.target = (Department)value; 
     return ((mask & this.target) != 0); 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) 
    { 
     this.target ^= (Department)parameter; 
     return this.target; 
    } 
} 

Ve sonra XAML dönüştürücü kullanın::

<Window.Resources> 
    <l:DepartmentValueConverter x:Key="DeptConverter" /> 
</Window.Resources> 

<StackPanel x:Name="DepartmentsPanel"> 
    <CheckBox Content="A" 
       IsChecked="{Binding 
          Path=Department, 
          Converter={StaticResource DeptConverter}, 
          ConverterParameter={x:Static l:Department.A}}"/> 
    <!-- more --> 
</StackPanel> 

DÜZENLEME İşte dönüştürücü daha genel hale nasıl görmek hedefe Enum için oldukça spesifik bir uygulama, ama zor olmaz : Kendi yorumumu güncellemek zorunda olduğum için yeterli "rep" (henüz!) Yok. (

Son yorumda demwiz.myopenid.com diyor ki "ama ikiye gelince ... ConvertBack'i bağlama yolu ayrı düşüyor ", ben de güncelledim d ConvertBack senaryosunu işlemek için yukarıdaki örnek kodum; Ayrıca bir örnek çalışma uygulaması gönderdim here (düzenleme: örnek kod indirme, dönüştürücünün genel bir sürümünü de içerir).

Şahsen bunun çok daha basit olduğunu düşünüyorum, umarım bu yardımcı olur.

+0

Öneri Paul için teşekkürler, ancak birden fazla onay kutusu varsa, bunlardan herhangi birinden ConvertBack geçersiz kılacak ve diğer bitlerin verilerini kaybedecektir. Bunu zor bir sorun haline getiren ConvertBack parçasıdır. –

+0

Gerçekten de, örnek biraz basittir; Ancak, bu çözüm gelen boole bakabileceğinden hala geçerli olduğunu düşünüyorum? değer ve sonra^= DönüştürücüParametre'de sağlanan maskeye dayalı değer; mantıklı olmak? Eğer lemme bilmiyorsanız ve tatillerde biraz zaman geçirdiğimde bazı kodları yayınlayacağım. – PaulJ

+0

Yayını, ConvertBack senaryosunu içerecek şekilde güncelledim, ayrıca uygulamanın çalışan bir kopyasına bir link gönderdiğimi de unutmayın. – PaulJ

1

CheckBox'ları bağlanan sizin DataObject kontrol Bölümü özelliği bir INotifyPropertyChnaged sahiptir içerir. PropertyChanged onun Setter çağırdı?

+0

Ben PropertyChanged olayları başarıyla yayımlayan bir çok yazılan DataRow bağlama yapıyorum. Bunu, doğru bir şekilde güncellenecek olan diğer UI denetimlerine (Label, TextBox) bağlayarak doğruladım. Öneriniz için teşekkürler. :) –

2

Herkesin yardımları için teşekkürler, sonunda anladım.

Çoktan yazılan bir DataSet'e bağlanıyorum, bu nedenle numaralandırmalar System.Enum türünde değil, System.Byte olarak depolanır. Hata ayıklama çıkış penceremde sessiz bir bağlama özel durumu fark ettim ve bu fark bana işaret etti. Çözüm, yukarıdakiyle aynıdır, ancak ValueProperty, Enum yerine Byte türünde bir Byte'dir.

İşte CheckBoxFlagsBehavior sınıfı, son düzeltmesinde yinelenir. Orijinal uygulama için tekrar Ian Oakes'a teşekkürler!

public class CheckBoxFlagsBehaviour 
{ 
    private static bool isValueChanging; 

    public static Enum GetMask(DependencyObject obj) 
    { 
     return (Enum)obj.GetValue(MaskProperty); 
    } // end GetMask 

    public static void SetMask(DependencyObject obj, Enum value) 
    { 
     obj.SetValue(MaskProperty, value); 
    } // end SetMask 

    public static readonly DependencyProperty MaskProperty = 
     DependencyProperty.RegisterAttached("Mask", typeof(Enum), 
     typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(null)); 

    public static byte GetValue(DependencyObject obj) 
    { 
     return (byte)obj.GetValue(ValueProperty); 
    } // end GetValue 

    public static void SetValue(DependencyObject obj, byte value) 
    { 
     obj.SetValue(ValueProperty, value); 
    } // end SetValue 

    public static readonly DependencyProperty ValueProperty = 
     DependencyProperty.RegisterAttached("Value", typeof(byte), 
     typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(default(byte), ValueChanged)); 

    private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     isValueChanging = true; 
     byte mask = Convert.ToByte(GetMask(d)); 
     byte value = Convert.ToByte(e.NewValue); 

     BindingExpression exp = BindingOperations.GetBindingExpression(d, IsCheckedProperty); 
     object dataItem = GetUnderlyingDataItem(exp.DataItem); 
     PropertyInfo pi = dataItem.GetType().GetProperty(exp.ParentBinding.Path.Path); 
     pi.SetValue(dataItem, (value & mask) != 0, null); 

     ((CheckBox)d).IsChecked = (value & mask) != 0; 
     isValueChanging = false; 
    } // end ValueChanged 

    public static bool? GetIsChecked(DependencyObject obj) 
    { 
     return (bool?)obj.GetValue(IsCheckedProperty); 
    } // end GetIsChecked 

    public static void SetIsChecked(DependencyObject obj, bool? value) 
    { 
     obj.SetValue(IsCheckedProperty, value); 
    } // end SetIsChecked 

    public static readonly DependencyProperty IsCheckedProperty = 
     DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), 
     typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(false, IsCheckedChanged)); 

    private static void IsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     if (isValueChanging) return; 

     bool? isChecked = (bool?)e.NewValue; 
     if (isChecked != null) 
     { 
      BindingExpression exp = BindingOperations.GetBindingExpression(d, ValueProperty); 
      object dataItem = GetUnderlyingDataItem(exp.DataItem); 
      PropertyInfo pi = dataItem.GetType().GetProperty(exp.ParentBinding.Path.Path); 

      byte mask = Convert.ToByte(GetMask(d)); 
      byte value = Convert.ToByte(pi.GetValue(dataItem, null)); 

      if (isChecked.Value) 
      { 
       if ((value & mask) == 0) 
       { 
        value = (byte)(value + mask); 
       } 
      } 
      else 
      { 
       if ((value & mask) != 0) 
       { 
        value = (byte)(value - mask); 
       } 
      } 

      pi.SetValue(dataItem, value, null); 
     } 
    } // end IsCheckedChanged 

    private static object GetUnderlyingDataItem(object o) 
    { 
     return o is DataRowView ? ((DataRowView)o).Row : o; 
    } // end GetUnderlyingDataItem 
} // end class CheckBoxFlagsBehaviour 
+0

Bu çok karmaşık görünüyor - neden basit bir değer dönüştürücü –

+1

Bir değer dönüştürücü, tek yönlü bağlanma için mükemmeldir, ancak iki yönlü bağlanma söz konusu olduğunda, ConvertBack ayrı düştüğünden, geçerli bir değer döndürmek için diğer bitlerin nasıl ayarlanacağını bilemezsiniz. –

İlgili konular