2011-02-05 3 views
3

Я пытаюсь привязать свойство 444 только для чтения для логического свойства чтения/записи в моей модели представления.Databind Свойство зависимостей только для чтения ViewModel в Xaml

В принципе, мне нужно значение свойства IsMouseOver Button, которое нужно прочитать, чтобы посмотреть на модель модели.

<Button IsMouseOver="{Binding Path=IsMouseOverProperty, Mode=OneWayToSource}" /> 

Я получаю ошибку компиляции: свойство «IsMouseOver» только для чтения и не может быть установлен из разметки. Что я делаю неправильно?

ответ

5

Ошибка при проезде. Это limitation of WPF - свойство только для чтения не может быть связано OneWayToSource, если источник также не является DependencyProperty.

Альтернативой является приложенное поведение.

+0

Можете ли вы уточнить, что вы имеете в виду «если источник не является также DependencyProperty». Я уверен, что вы не можете этого сделать, или я ошибаюсь здесь? –

+1

+1, я попробовал это, и он работает, как вы сказали :) Только кажется, что работает от кода, хотя и не Xaml. Хорошо знать! –

+0

Большое спасибо Кенту. Это очень полезно. Кстати ... его очень приятно слышать от кого-то вроде тебя .. Я столкнулся с рядом полезных технических статей. Очень признателен. –

0

Как многие люди упомянули об этом, это ошибка в WPF, и лучший способ - это прикрепить свойство, подобное предложению Тима/Кента. Вот прикрепленное свойство, которое я использую в своем проекте. Я намеренно делаю это таким образом для удобства чтения, модульности и привязки к MVVM без кода в представлении, чтобы обрабатывать события вручную повсюду.

public interface IMouseOverListener 
{ 
    void SetIsMouseOver(bool value); 
} 
public static class ControlExtensions 
{ 
    public static readonly DependencyProperty MouseOverListenerProperty = 
     DependencyProperty.RegisterAttached("MouseOverListener", typeof (IMouseOverListener), typeof (ControlExtensions), new PropertyMetadata(OnMouseOverListenerChanged)); 

    private static void OnMouseOverListenerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var element = ((UIElement)d); 

     if(e.OldValue != null) 
     { 
      element.MouseEnter -= ElementOnMouseEnter; 
      element.MouseLeave -= ElementOnMouseLeave; 
     } 

     if(e.NewValue != null) 
     { 
      element.MouseEnter += ElementOnMouseEnter; 
      element.MouseLeave += ElementOnMouseLeave; 
     } 
    } 

    public static void SetMouseOverListener(UIElement element, IMouseOverListener value) 
    { 
     element.SetValue(MouseOverListenerProperty, value); 
    } 

    public static IMouseOverListener GetMouseOverListener(UIElement element) 
    { 
     return (IMouseOverListener) element.GetValue(MouseOverListenerProperty); 
    } 

    private static void ElementOnMouseLeave(object sender, MouseEventArgs mouseEventArgs) 
    { 
     var element = ((UIElement)sender); 
     var listener = GetMouseOverListener(element); 
     if(listener != null) 
      listener.SetIsMouseOver(false); 
    } 

    private static void ElementOnMouseEnter(object sender, MouseEventArgs mouseEventArgs) 
    { 
     var element = ((UIElement)sender); 
     var listener = GetMouseOverListener(element); 
     if (listener != null) 
      listener.SetIsMouseOver(true); 
    } 

} 
0

Вот приблизительный пример того, к чему я прибегал, ища общее решение этой проблемы. В нем используется форматирование css-типа, чтобы указать свойства зависимостей для привязки к свойствам модели (модели, полученные из DataContext); это также означает, что он будет работать только на FrameworkElements.
Я не проверил его полностью, но счастливый путь работает просто отлично для нескольких тестовых случаев, которые я запускал.

public class BindingInfo 
{ 
    internal string sourceString = null; 
    public DependencyProperty source { get; internal set; } 
    public string targetProperty { get; private set; } 

    public bool isResolved => source != null; 

    public BindingInfo(string source, string target) 
    { 
     this.sourceString = source; 
     this.targetProperty = target; 
     validate(); 
    } 
    private void validate() 
    { 
     //verify that targetProperty is a valid c# property access path 
     if (!targetProperty.Split('.') 
          .All(p => Identifier.IsMatch(p))) 
      throw new Exception("Invalid target property - " + targetProperty); 

     //verify that sourceString is a [Class].[DependencyProperty] formatted string. 
     if (!sourceString.Split('.') 
         .All(p => Identifier.IsMatch(p))) 
      throw new Exception("Invalid source property - " + sourceString); 
    } 

    private static readonly Regex Identifier = new Regex(@"[_a-z][_\w]*$", RegexOptions.IgnoreCase); 
} 

[TypeConverter(typeof(BindingInfoConverter))] 
public class BindingInfoGroup 
{ 
    private List<BindingInfo> _infoList = new List<BindingInfo>(); 
    public IEnumerable<BindingInfo> InfoList 
    { 
     get { return _infoList.ToArray(); } 
     set 
     { 
      _infoList.Clear(); 
      if (value != null) _infoList.AddRange(value); 
     } 
    } 
} 

public class BindingInfoConverter: TypeConverter 
{ 
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) 
    { 
     if (sourceType == typeof(string)) return true; 
     return base.CanConvertFrom(context, sourceType); 
    } 

    // Override CanConvertTo to return true for Complex-to-String conversions. 
    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) 
    { 
     if (destinationType == typeof(string)) return true; 
     return base.CanConvertTo(context, destinationType); 
    } 

    // Override ConvertFrom to convert from a string to an instance of Complex. 
    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) 
    { 
     string text = value as string; 
     return new BindingInfoGroup 
     { 
      InfoList = text.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) 
          .Select(binfo => 
          { 
           var parts = binfo.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries); 
           if (parts.Length != 2) throw new Exception("invalid binding info - " + binfo); 
           return new BindingInfo(parts[0].Trim(), parts[1].Trim()); 
          }) 
     }; 
    } 

    // Override ConvertTo to convert from an instance of Complex to string. 
    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, 
            object value, Type destinationType) 
    { 
     var bgroup = value as BindingInfoGroup; 
     return bgroup.InfoList 
        .Select(bi => $"{bi.sourceString}:{bi.targetProperty};") 
        .Aggregate((n, p) => n += $"{p} ") 
        .Trim(); 
    } 

    public override bool GetStandardValuesSupported(ITypeDescriptorContext context) => false; 
} 

public class Bindings 
{ 
    #region Fields 
    private static ConcurrentDictionary<DependencyProperty, PropertyChangeHandler> _Properties = 
        new ConcurrentDictionary<DependencyProperty, PropertyChangeHandler>(); 

    #endregion 


    #region OnewayBindings 
    public static readonly DependencyProperty OnewayBindingsProperty = 
     DependencyProperty.RegisterAttached("OnewayBindings", typeof(BindingInfoGroup), typeof(Bindings), new FrameworkPropertyMetadata 
     { 
      DefaultValue = null, 
      PropertyChangedCallback = (x, y) => 
      { 
       var fwe = x as FrameworkElement; 
       if (fwe == null) return; 

       //resolve the bindings 
       resolve(fwe); 

       //add change delegates 
       (GetOnewayBindings(fwe)?.InfoList ?? new BindingInfo[0]) 
       .Where(bi => bi.isResolved) 
       .ToList() 
       .ForEach(bi => 
       { 
        var descriptor = DependencyPropertyDescriptor.FromProperty(bi.source, fwe.GetType()); 
        PropertyChangeHandler listener = null; 
        if (_Properties.TryGetValue(bi.source, out listener)) 
        { 
         descriptor.RemoveValueChanged(fwe, listener.callback); //cus there's no way to check if it had one before... 
         descriptor.AddValueChanged(fwe, listener.callback); 
        } 
       }); 
      } 
     }); 

    private static void resolve(FrameworkElement element) 
    { 
     var bgroup = GetOnewayBindings(element); 
     bgroup.InfoList 
       .ToList() 
       .ForEach(bg => 
       { 
        //source 
        var sourceParts = bg.sourceString.Split('.'); 
        if (sourceParts.Length == 1) 
        { 
         bg.source = element.GetType() 
             .baseTypes() //<- flattens base types, including current type 
             .SelectMany(t => t.GetRuntimeFields() 
                  .Where(p => p.IsStatic) 
                  .Where(p => p.FieldType == typeof(DependencyProperty))) 
             .Select(fi => fi.GetValue(null) as DependencyProperty) 
             .FirstOrDefault(dp => dp.Name == sourceParts[0]) 
             .ThrowIfNull($"Dependency Property '{sourceParts[0]}' was not found"); 
        } 
        else 
        { 
         //resolve the dependency property [ClassName].[PropertyName]Property - e.g FrameworkElement.DataContextProperty 
         bg.source = Type.GetType(sourceParts[0]) 
             .GetField(sourceParts[1]) 
             .GetValue(null) 
             .ThrowIfNull($"Dependency Property '{bg.sourceString}' was not found") as DependencyProperty; 
        } 

        _Properties.GetOrAdd(bg.source, ddp => new PropertyChangeHandler { property = ddp }); //incase it wasnt added before. 
       }); 
    } 


    public static BindingInfoGroup GetOnewayBindings(FrameworkElement source) 
     => source.GetValue(OnewayBindingsProperty) as BindingInfoGroup; 
    public static void SetOnewayBindings(FrameworkElement source, string value) 
     => source.SetValue(OnewayBindingsProperty, value); 
    #endregion 

} 

public class PropertyChangeHandler 
{ 
    internal DependencyProperty property { get; set; } 

    public void callback(object obj, EventArgs args) 
    { 
     var fwe = obj as FrameworkElement; 
     var target = fwe.DataContext; 
     if (fwe == null) return; 
     if (target == null) return; 

     var bg = Bindings.GetOnewayBindings(fwe); 
     if (bg == null) return; 
     else bg.InfoList 
       .Where(bi => bi.isResolved) 
       .Where(bi => bi.source == property) 
       .ToList() 
       .ForEach(bi => 
       { 
        //transfer data to the object 
        var data = fwe.GetValue(property); 
        KeyValuePair<object, PropertyInfo>? pinfo = resolveProperty(target, bi.targetProperty); 
        if (pinfo == null) return; 
        else pinfo.Value.Value.SetValue(pinfo.Value.Key, data); 
       }); 

    } 
    private KeyValuePair<object, PropertyInfo>? resolveProperty(object target, string path) 
    { 
     try 
     { 
      var parts = path.Split('.'); 
      if (parts.Length == 1) return new KeyValuePair<object, PropertyInfo>(target, target.GetType().GetProperty(parts[0])); 
      else //(parts.Length>1) 
       return resolveProperty(target.GetType().GetProperty(parts[0]).GetValue(target), 
             string.Join(".", parts.Skip(1))); 
     } 
     catch (Exception e) //too lazy to care :D 
     { 
      return null; 
     } 
    } 
} 

И использовать XAML ...

<Grid ab:Bindings.OnewayBindings="IsMouseOver:mouseOver;">...</Grid> 
Смежные вопросы