没有setter的viewmodel中的WPF mvvm属性?

时间:2015-04-08 20:38:53

标签: c# wpf mvvm

我正在处理使用和坚持MVVM模式的一些WPF问题。

我的大部分属性都是这样的:

public string Period
{
    get { return _primaryModel.Period; }
    set
    {
        if (_primaryModel.Period != value)
        {
            _primaryModel.Period = value;
            RaisePropertyChanged("Period");
        }
    }
}

这非常好。

但是我也有一些这样的属性:

public bool EnableConsignor
{
    get
    {
        return (ConsignorViewModel.Id != 0);
    }
}

它没有setter,因为id“自动”更改(每次调用ConsignorViewModel的保存时。但这会导致“系统”不知道何时bool的问题从false更改为true(因为没有调用RaisePropertyChanged)。

2 个答案:

答案 0 :(得分:4)

对于这些类型的属性,您需要在更改从属数据时引发PropertyChanged。类似的东西:

public object ConsignorViewModel
{
   get { return consignorViewModel; }
   set
   {
       consignorViewModel = value;
       RaisePropertyChanged("ConsignorViewModel");
       RaisePropertyChanged("EnableConsignor");
   } 
}
可以在任何方法中调用

RaisePropertyChanged,因此只需执行任何会改变EnableConsignor返回值的操作。以上只是一个例子。

答案 1 :(得分:2)

我刚刚写了这篇文章,它一直很好用

[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class CalculatedProperty : Attribute
{
    private string[] _props;
    public CalculatedProperty(params string[] props)
    {
        this._props = props;
    }

    public string[] Properties
    {
        get
        {
            return _props;
        }
    }
}

ViewModel基础

public class ObservableObject : INotifyPropertyChanged
{
    private static Dictionary<string, Dictionary<string, string[]>> calculatedPropertiesOfTypes = new Dictionary<string, Dictionary<string, string[]>>();

    private readonly bool hasComputedProperties;
    public ObservableObject()
    {
        Type t = GetType();
        if (!calculatedPropertiesOfTypes.ContainsKey(t.FullName))
        {
            var props = t.GetProperties();

            foreach (var pInfo in props)
            {
                var attr = pInfo.GetCustomAttribute<CalculatedProperty>(false);
                if (attr == null)
                    continue;

                if (!calculatedPropertiesOfTypes.ContainsKey(t.FullName))
                {
                    calculatedPropertiesOfTypes[t.FullName] = new Dictionary<string, string[]>();
                }
                calculatedPropertiesOfTypes[t.FullName][pInfo.Name] = attr.Properties;
            }
        }

        if (calculatedPropertiesOfTypes.ContainsKey(t.FullName))
            hasComputedProperties = true;
    }


    public event PropertyChangedEventHandler PropertyChanged;
    public virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));

        if (this.hasComputedProperties)
        {
            //check for any computed properties that depend on this property
            var computedPropNames =
                calculatedPropertiesOfTypes[this.GetType().FullName]
                .Where(kvp => kvp.Value.Contains(propertyName))
                .Select(kvp => kvp.Key);

            if (computedPropNames != null)
                if (!computedPropNames.Any())
                    return;

            //raise property changed for every computed property that is dependant on the property we did just set
            foreach (var computedPropName in computedPropNames)
            {
                //to avoid stackoverflow as a result of infinite recursion if a property depends on itself!
                if (computedPropName == propertyName)
                  throw new InvalidOperationException("A property can't depend on itself");

                OnPropertyChanged(computedPropName);
            }
        }
    }


    protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value))
            return false;

        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

示例:

public class ViewModel : ObservableObject
{
    private int _x;
    public int X
    {
        get { return _x; }
        set { SetField(ref _x, value); }
    }

    private int _y;

    public int Y
    {
        get { return _y; }
        set { SetField(ref _y, value); }

    }

    //use the CalculatedProperty annotation for properties that depend on other properties and pass it the prop names that it depends on
    [CalculatedProperty("X", "Y")]
    public int Z
    {
        get { return X * Y; }
    }

    [CalculatedProperty("Z")]
    public int M
    {
        get { return Y * Z; }
    }

}

请注意:

  • 每种类型只使用一次反射
  • SetField设置字段,如果有新值,则会提升属性
  • 只要您在设置器中调用属性名称,就不需要将属性名称传递给SetField,因为 {c> 5.0,[CallerMemberName]为你做了这件事。
  • 如果您在二传手机外拨打SetField,则必须通过 属性名称
  • 根据我上次更新,您可以通过直接设置字段来避免使用SetField 调用OnPropertyChanged(“PropertyName”),它将引发 PropertyChanged用于所有属性 依赖于它。
  • 在c#6中,您可以使用nameof运算符来获取属性名称 如nameof(Property)
  • 如果计算出来的话,
  • OnPropertyChanged会自行调用 特性

XAML进行测试

<StackPanel>
    <TextBox Text="{Binding X,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
    <TextBox Text="{Binding Y,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></TextBox>
    <TextBlock Text="{Binding Z,Mode=OneWay}"></TextBlock>
    <TextBlock Text="{Binding M,Mode=OneWay}"></TextBlock>
</StackPanel>