2016-09-06 4 views
0

私はWPFアプリケーションを持っています。 MVVMパターンを使用しています。私は辞書があります。ディクショナリが埋められていた場合、辞書が正しく動作しないバインド

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

     ... 
} 

をし、私はそれはそうのようなメニュー項目の可視性に値だバインドする:辞書の要素は情報が含まれているので、私はウィンドウがビジュアルツリーを構築する必要がある。この辞書を埋めるため

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

ウィンドウMenuItemから。もし私がInitializeComponentの前に辞書を作成すると、VisualTreeHelper.GetChildrenCountがゼロ要素を返すので、値がsystemMenuItemのキーが存在しないという例外が得られます。もし私がLoadedイベントでそれをやろうとすれば、私は普通の充満した辞書を得るでしょうが、その場合はバインディングが動作しません。ウィンドウがユーザに表示され、MenuItemsから情報を取得する前に、どのように辞書を塗りつぶすことができますか?そして、私はどうすれば拘束力のある仕事をすることができますか?ウィンドウがメインとスタートアップです。

+1

変更可能な辞書にバインドする場合は、バインド可能な辞書の実装を使用する必要があります。 –

+2

@HB:http://blogs.microsoft.co.il/shimmy/2010/12/26/observabledictionarylttkey-tvaluegt-c/ – Kilazur

+0

で展開するとわかりましたが、結果は同じです。私はちょうど辞書のタイプを変更して、他はすべて同じです。バインディングは 'Loaded'ではまだ動作しておらず、' InitializeComponent() 'の前にObservableDictionaryを作成している場合は例外がスローされます。 –

答えて

0

近くに行くアウトとプロパティとして公開されます、私は彼らの提案で私を助けた皆に感謝伝えたいです溶液を見つける。 私は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(); 
     } 
    } 

InitializeComponentが進行しているときに例外がスローされていません。このクラスの主なアイデア。それは私にバインディングがそれから価値を得ることを試みる前に辞書のインスタンスを作成する機会を与えます。これはメインウィンドウのコンストラクタで行われていることです:

public MainWindow() 
{ 
    ViewModelBase.Permissions = new SilentDictionary<string, Action>(); 
    InitializeComponent(); 
} 

今すぐバインディングにはDependencyProperty.UnsetValueがありません。バインディングは私の静的なSilentDictionaryインスタンスとの接続を持っています。次のステップは、静的辞書のプロパティの変更を通知することです:

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 
} 

と私は初めに計画通り、最終的に、私はこの辞書にバインドすることができます。

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

そして、すべてが期待どおりにうまく動作します。

1

あなたはこのようなマルチ値変換定義することができます。これは、再することです以上ないそのうちいくつかの利点を提供します

<MenuItem x:Name="systemMenuItem" Header="System"> 
    <MenuItem.Visibility> 
     <MultiBinding Converter="{StaticResource ElementPermissionToVisibilityConverter}"> 
      <Binding Path="Permissions"/> 
      <Binding RelativeSource="{RelativeSource Self}"/> 
     </MultiBinding> 
    </MenuItem.Visibility> 
</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(); 
    } 
} 

そして、このようにXAMLでそれを使用しますPermissionsプロパティが変更された場合、バインディングを評価します。

明らかにこれは非常に冗長ですが、バインディングをMenuItemスタイルにラップして一度定義するだけで済みます。

+0

初めてのことです。しかし、私が価値を変えたいのであれば、それは機能しません。私は 'Dictionary'と' ObservableDictionary'を試しました。この解決策で解決できるのでしょうか?例えば、 'Loaded'イベントでは次のようなことをしています:' ViewModelBase.Permissions.Add( "systemMenuItem"、new Security.ActionBased.Action()); ViewModelBase.Permissions ["systemMenuItem"]。CanBeShow = true; ' –

+0

パーミッションを変更した後、PermissionsプロパティのPropertyChangedを呼び出します。権限を再評価し、UIを更新する必要があります。 – ibebbs

+0

解決していただきありがとうございます1働いていますが、私はあなたの勧告と勧告に基づいて別のものを作っています。私はそれを回答者として書くつもりです。 –

3

一般的なルールとしてバインディング用の辞書を使用することは悪い考えです。これはおそらくMSがObservable Dictionaryを.Netフレームワークの一部として作成したことがないため、Permissionクラスを作成してそれらの監視可能なコレクションは、これはあなたのバインディングの両方のコレクションを与え、許可を拘束変わります

注:これは、以前のバージョンである場合、あなたはそれを

例を微調整する必要があるかもしれませんので、C#6を使用しています

xaml

<Window x:Class="WpfApplication1.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:local="clr-namespace:WpfApplication1" 
     mc:Ignorable="d" 
     Title="MainWindow" Height="350" Width="525"> 
    <Window.Resources> 
     <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/> 
    </Window.Resources> 
    <Window.DataContext> 
     <local:ViewModel/> 
    </Window.DataContext> 
    <DockPanel> 
     <Menu DockPanel.Dock="Top" ItemsSource="{Binding Permissions}"> 
      <Menu.ItemContainerStyle> 
       <Style TargetType="MenuItem"> 
        <Setter Property="Header" Value="{Binding Name}"/> 
        <Setter Property="Visibility" Value="{Binding CanBeShow, Converter={StaticResource BooleanToVisibilityConverter}}"/> 
        <Setter Property="Command" Value="{Binding Action}"/> 
       </Style> 
      </Menu.ItemContainerStyle> 
     </Menu> 
     <Grid/> 
    </DockPanel> 
</Window> 

ビューモデル

public class ViewModel 
{ 
    public ViewModel() 
    { 
     //some dummy data 
     Permissions.Add(new Permission() 
     { 
      Name = "Open", 
      CanBeShow = true, 
      Action = ApplicationCommands.Open 
     }); 
     Permissions.Add(new Permission() 
     { 
      Name = "Save", 
      CanBeShow = false, 
      Action = ApplicationCommands.Save 
     }); 
     Permissions.Add(new Permission() 
     { 
      Name = "Delete", 
      CanBeShow = true, 
      Action = ApplicationCommands.Delete 
     }); 
    } 

    public ObservableCollection<Permission> Permissions { get; } = new ObservableCollection<Permission>(); 
    //notice no set you want to change the content of the collection not the collection 
} 

public class Permission:INotifyPropertyChanged 
{ 
    private string name; 

    public string Name 
    { 
     get { return name; } 
     set 
     { 
      name = value; 
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name))); 
     } 
    } 

    private bool canBeShow; 

    public bool CanBeShow 
    { 
     get { return canBeShow; } 
     set 
     { 
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CanBeShow))); 
      canBeShow = value; 
     } 
    } 


    private ICommand action; 
    public ICommand Action 
    { 
     get { return action; } 
     set { 
      action = value; 
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Action))); 
     } 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
} 

しかし、あなたは、メニュー・アクション、 のセキュリティのいくつかの種類を実装しようとしているならば、これを行うための好ましい方法は、そのカスタムコマンドを作成するためのICommandを上書きするだろうそれ自身の権限を検索し、表示

まず
public class PermissionCommand:INotifyPropertyChanged,ICommand 
{ 
    public event EventHandler CanExecuteChanged; 
    public event PropertyChangedEventHandler PropertyChanged; 

    private string name; 

    public string Name 
    { 
     get { return name; } 
     set 
     { 
      name = value; 
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name))); 
     } 
    } 

    public bool CanBeShow 
    { 
     get 
     { 
      //Check permissions for CanBeShow here 
     } 
    } 

    public bool CanExecute(object parameter) 
    { 
     //Check permissions for Execution here 
    } 

    public void Execute(object parameter) 
    { 
     // perform action here 
    } 
    //you will need to trigger the events when the permissions change 
} 
+0

ありがとうございます!それは興味深い解決策であり、私は次のプロジェクトでそれを使用することについて考えますが、今のところ私は別の解決策を見つけました。 –

関連する問題