C# 如果字典填写过迟,则绑定不能与字典一起使用

C# 如果字典填写过迟,则绑定不能与字典一起使用,c#,wpf,mvvm,binding,C#,Wpf,Mvvm,Binding,我有一个WPF应用程序。我使用的是MVVM模式。我有一本字典: public abstract class ViewModelBase { public static Dictionary<string, Action> Permissions { get; set; } ... } public abstract class ViewModelBase { protected static SilentDictionary<s

我有一个WPF应用程序。我使用的是MVVM模式。我有一本字典:

 public abstract class ViewModelBase
 {
        public static Dictionary<string, Action> Permissions { get; set; }

        ...
 }
public abstract class ViewModelBase
{
    protected static SilentDictionary<string, Action> _permissions;

    public static SilentDictionary<string, Action> Permissions
    {
        get { return _permissions; }
        set
        {
            _permissions = value;
            NotifyPropertyChanged();
        }
    }  

    public static event EventHandler PermissionsChanged;

    protected static void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
        EventHandler temp = Volatile.Read(ref PermissionsChanged);
        PermissionsChanged?.Invoke(null, new EventArgs());
    }

    // other code
}
公共抽象类ViewModelBase
{
公共静态字典权限{get;set;}
...
}
我想将它的值绑定到菜单项的可见性,如下所示:

<MenuItem x:Name="systemMenuItem" Header="System" Visibility="{Binding Permissions[systemMenuItem].CanBeShow, Converter={StaticResource BoolToVis}}">...</MenuItem>
。。。

要填充此字典,我需要窗口构建可视化树,因为字典的元素包含来自窗口菜单项的信息。如果我在初始化Component之前创建dictionary,我将得到一个异常,即没有值为
systemMenuItem
的键,因为
VisualTreeHelper.GetChildrenCount
返回零元素。如果我在
Loaded
事件中这样做,我将得到正常的填充字典,但在这种情况下绑定不起作用。在向用户显示窗口并从菜单项获取信息之前,我如何填写词典?在这种情况下,我怎样才能使我的装订工作正常呢?该窗口是主窗口和启动窗口。

您可以定义如下的多值转换器:

public class ElementPermissionToVisibilityConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        Dictionary<string, Action> permissions = values.OfType<Dictionary<string, Action>>().FirstOrDefault();
        FrameworkElement element = values.OfType<FrameworkElement>().FirstOrDefault();

        if (permissions != null && element != null && !string.IsNullOrWhiteSpace(element.Name))
        {
            Action action;

            if (permissions.TryGetValue(element.Name, out action))
            {
                return action.CanBeShown ? Visibility.Visible : Visibility.Collapsed;
            }
            else
            {
                return Visibility.Collapsed;
            }
        }
        else
        {
            return DependencyProperty.UnsetValue;
        }
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}
 <MenuItem x:Name="systemMenuItem" Header="System">
    <MenuItem.Visibility>
        <MultiBinding Converter="{StaticResource ElementPermissionToVisibilityConverter}">
            <Binding Path="Permissions"/>
            <Binding RelativeSource="{RelativeSource Self}"/>
        </MultiBinding>
    </MenuItem.Visibility>
</MenuItem>
公共类元素PermissionToVisibilityConverter:IMultiValueConverter
{
公共对象转换(对象[]值,类型targetType,对象参数,CultureInfo区域性)
{
字典权限=value.OfType().FirstOrDefault();
FrameworkElement元素=value.OfType().FirstOrDefault();
if(权限!=null&&element!=null&&string.IsNullOrWhiteSpace(element.Name))
{
行动;
if(permissions.TryGetValue(element.Name,out操作))
{
return action.CanBeShown?可见性。可见:可见性。折叠;
}
其他的
{
返回可见性。折叠;
}
}
其他的
{
返回dependencProperty.unset值;
}
}
公共对象[]转换回(对象值,类型[]目标类型,对象参数,CultureInfo区域性)
{
抛出新的NotSupportedException();
}
}
并在XAML中使用它,如下所示:

public class ElementPermissionToVisibilityConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        Dictionary<string, Action> permissions = values.OfType<Dictionary<string, Action>>().FirstOrDefault();
        FrameworkElement element = values.OfType<FrameworkElement>().FirstOrDefault();

        if (permissions != null && element != null && !string.IsNullOrWhiteSpace(element.Name))
        {
            Action action;

            if (permissions.TryGetValue(element.Name, out action))
            {
                return action.CanBeShown ? Visibility.Visible : Visibility.Collapsed;
            }
            else
            {
                return Visibility.Collapsed;
            }
        }
        else
        {
            return DependencyProperty.UnsetValue;
        }
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}
 <MenuItem x:Name="systemMenuItem" Header="System">
    <MenuItem.Visibility>
        <MultiBinding Converter="{StaticResource ElementPermissionToVisibilityConverter}">
            <Binding Path="Permissions"/>
            <Binding RelativeSource="{RelativeSource Self}"/>
        </MultiBinding>
    </MenuItem.Visibility>
</MenuItem>

这将提供几个优点,其中最重要的是在权限属性更改时重新评估绑定


显然,这相当冗长,但您可以将绑定包装为MenuItem样式,因此只需定义一次。

您可以定义如下多值转换器:

public class ElementPermissionToVisibilityConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        Dictionary<string, Action> permissions = values.OfType<Dictionary<string, Action>>().FirstOrDefault();
        FrameworkElement element = values.OfType<FrameworkElement>().FirstOrDefault();

        if (permissions != null && element != null && !string.IsNullOrWhiteSpace(element.Name))
        {
            Action action;

            if (permissions.TryGetValue(element.Name, out action))
            {
                return action.CanBeShown ? Visibility.Visible : Visibility.Collapsed;
            }
            else
            {
                return Visibility.Collapsed;
            }
        }
        else
        {
            return DependencyProperty.UnsetValue;
        }
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}
 <MenuItem x:Name="systemMenuItem" Header="System">
    <MenuItem.Visibility>
        <MultiBinding Converter="{StaticResource ElementPermissionToVisibilityConverter}">
            <Binding Path="Permissions"/>
            <Binding RelativeSource="{RelativeSource Self}"/>
        </MultiBinding>
    </MenuItem.Visibility>
</MenuItem>
公共类元素PermissionToVisibilityConverter:IMultiValueConverter
{
公共对象转换(对象[]值,类型targetType,对象参数,CultureInfo区域性)
{
字典权限=value.OfType().FirstOrDefault();
FrameworkElement元素=value.OfType().FirstOrDefault();
if(权限!=null&&element!=null&&string.IsNullOrWhiteSpace(element.Name))
{
行动;
if(permissions.TryGetValue(element.Name,out操作))
{
return action.CanBeShown?可见性。可见:可见性。折叠;
}
其他的
{
返回可见性。折叠;
}
}
其他的
{
返回dependencProperty.unset值;
}
}
公共对象[]转换回(对象值,类型[]目标类型,对象参数,CultureInfo区域性)
{
抛出新的NotSupportedException();
}
}
并在XAML中使用它,如下所示:

public class ElementPermissionToVisibilityConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        Dictionary<string, Action> permissions = values.OfType<Dictionary<string, Action>>().FirstOrDefault();
        FrameworkElement element = values.OfType<FrameworkElement>().FirstOrDefault();

        if (permissions != null && element != null && !string.IsNullOrWhiteSpace(element.Name))
        {
            Action action;

            if (permissions.TryGetValue(element.Name, out action))
            {
                return action.CanBeShown ? Visibility.Visible : Visibility.Collapsed;
            }
            else
            {
                return Visibility.Collapsed;
            }
        }
        else
        {
            return DependencyProperty.UnsetValue;
        }
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}
 <MenuItem x:Name="systemMenuItem" Header="System">
    <MenuItem.Visibility>
        <MultiBinding Converter="{StaticResource ElementPermissionToVisibilityConverter}">
            <Binding Path="Permissions"/>
            <Binding RelativeSource="{RelativeSource Self}"/>
        </MultiBinding>
    </MenuItem.Visibility>
</MenuItem>

这将提供几个优点,其中最重要的是在权限属性更改时重新评估绑定


显然,这相当冗长,但您可以将绑定包装为MenuItem样式,因此只需定义一次。

一般来说,使用字典进行绑定是个坏主意,这可能就是MS从未创建可观察字典作为.Net framework的一部分的原因,您最好创建一个权限类,然后创建一个可观察的权限类集合,这将为您提供权限的集合绑定和更改绑定

注意:这使用C#6,因此如果您使用的是早期版本,则可能需要对其进行调整

范例

Xaml


由于使用字典进行绑定的一般规则是一个坏主意,这可能就是MS从未将可观察字典创建为.Net framework的一部分的原因,您最好创建一个权限类,然后创建它们的可观察集合,这将为您提供集合绑定和权限上的更改绑定

注意:这使用C#6,因此如果您使用的是早期版本,则可能需要对其进行调整

范例

Xaml


首先,我想对所有帮助我找到解决方案的人说声谢谢。 我创建了
SilentDictionary
类,并使用它来代替
Dictionary

public class SilentDictionary<TKey, TValue> : IDictionary<TKey, TValue>
        where TValue : class, new()
    {
        protected IDictionary<TKey, TValue> Dictionary;

        public SilentDictionary()
        {
            Dictionary = new Dictionary<TKey, TValue>();
        }

        public TValue this[TKey key]
        {
            get
            {
                TValue item;
                if (Dictionary.TryGetValue(key, out item))
                    return item;
                else
                {
                    item = new TValue();
                    Add(key, item);
                    return item;
                }
            }
            set
            {
                Add(key, value);
            }
        }

        public int Count
        {
            get
            {
                return Dictionary.Count;
            }
        }

        public bool IsReadOnly
        {
            get
            {
                return Dictionary.IsReadOnly;
            }
        }

        public ICollection<TKey> Keys
        {
            get
            {
                return Dictionary.Keys;
            }
        }

        public ICollection<TValue> Values
        {
            get
            {
                return Dictionary.Values;
            }
        }

        public void Add(KeyValuePair<TKey, TValue> item)
        {
            Add(item.Key, item.Value);
        }

        public void Add(TKey key, TValue value)
        {
            TValue item;
            if (Dictionary.TryGetValue(key, out item))
            {                
                Type t = typeof(TValue);
                var props = t.GetProperties();
                foreach (var p in props)
                    p.SetValue(item, p.GetValue(value));
            }
            else
                Dictionary.Add(key, value);
        }

        public void Clear()
        {
            Dictionary.Clear();
        }

        public bool Contains(KeyValuePair<TKey, TValue> item)
        {
            return Dictionary.Contains(item);
        }

        public bool ContainsKey(TKey key)
        {
            return Dictionary.ContainsKey(key);
        }

        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
        {
            Dictionary.CopyTo(array, arrayIndex);
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
        {
            return Dictionary.GetEnumerator();
        }

        public bool Remove(KeyValuePair<TKey, TValue> item)
        {
            return Dictionary.Remove(item);
        }

        public bool Remove(TKey key)
        {
            return Dictionary.Remove(key);
        }

        public bool TryGetValue(TKey key, out TValue value)
        {
            return Dictionary.TryGetValue(key, out value);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable)Dictionary).GetEnumerator();
        }
    }
现在绑定没有
DependencyProperty.UnsetValue
。绑定与我的静态
SilentDictionary
实例有关联。下一步是对dictionary进行静态notify属性更改:

 public abstract class ViewModelBase
 {
        public static Dictionary<string, Action> Permissions { get; set; }

        ...
 }
public abstract class ViewModelBase
{
    protected static SilentDictionary<string, Action> _permissions;

    public static SilentDictionary<string, Action> Permissions
    {
        get { return _permissions; }
        set
        {
            _permissions = value;
            NotifyPropertyChanged();
        }
    }  

    public static event EventHandler PermissionsChanged;

    protected static void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
    {
        EventHandler temp = Volatile.Read(ref PermissionsChanged);
        PermissionsChanged?.Invoke(null, new EventArgs());
    }

    // other code
}
公共抽象类ViewModelBase
{
受保护的静态SilentDictionary\u权限;
公共静态SilentDictionary权限
{
获取{return\u permissions;}
设置
{
_权限=值;
NotifyPropertyChanged();
}
}  
公共静态事件处理程序权限更改;
受保护的静态void NotifyPropertyChanged([CallerMemberName]字符串propertyName=”“)
{
EventHandler temp=Volatile.Read(参考权限更改);
PermissionsChanged?.Invoke(null,new EventArgs());
}
//其他代码
}
最后,我可以按照开始时的计划绑定到这本词典:

<MenuItem x:Name="systemMenuItem" Header="Система" Visibility="{Binding Permissions[systemMenuItem].CanBeShown, Converter={StaticResource BoolToVis}}">

而且一切正常。

首先