2012-09-27 93 views
0

我是初學WPF。我在我的工作中開發一個新項目,我需要插入一個具有多個選擇的文件瀏覽器控件。基於多選樹視圖實現文件瀏覽器

概念需要類似的Acronis文件瀏覽器(與複選框樹視圖)

See Example

看一下左邊的容器,我需要實現與此類似, 我HABE搜索了很多通過谷歌和我看到了很多的實現,但沒有什麼不是這樣的。

因爲我沒有很多WPF經驗,所以我很難開始。

你有一些提示或類似的項目可能會幫助我做到嗎?

我的項目基於MVVM DP。

感謝

回答

4

重塑TreeView的是很容易的,你開始你的收藏,你要綁定,即

<Grid> 
    <TreeView ItemsSource="{Binding Folders}"/> 
</Grid> 

然而,你這時就需要定義如何顯示您綁定的數據至。我假設你的項目只是一個IEnumerable(任何列表或數組)的FolderViewModels和FileViewModels(都有一個Name屬性),所以現在我們需要說明如何顯示這些。你這樣做,通過定義一個DataTemplate和,因爲這是一棵樹,我們使用一個HeirarchicalDataTemplate作爲還定義子項目

<Grid.Resources> 
    <HierarchicalDataTemplate DataType="{x:Type viewModel:FolderViewModel}" 
           ItemsSource="{Binding SubFoldersAndFiles}"> 
     <CheckBox Content="{Binding Name}"/> 
    </HierarchicalDataTemplate> 
<Grid.Resources/> 

文件是相同的,但不需要子項目

<HierarchicalDataTemplate DataType="{x:Type viewModel:FolderViewModel}"> 
    <CheckBox Content="{Binding Name}"/> 
</HierarchicalDataTemplate> 

因此將其所有在一起你

<Grid> 
    <Grid.Resources> 
     <HierarchicalDataTemplate DataType="{x:Type viewModel:FolderViewModel}" 
           ItemsSource="{Binding SubFoldersAndFiles}"> 
      <CheckBox Content="{Binding Name}"/> 
     </HierarchicalDataTemplate> 
     <HierarchicalDataTemplate DataType="{x:Type viewModel:FolderViewModel}"> 
      <CheckBox Content="{Binding Name}"/> 
     </HierarchicalDataTemplate> 
    <Grid.Resources/> 
    <TreeView ItemsSource="{Binding Folders}"/> 
</Grid> 

圖標 如果你想顯示的圖標,然後更改日e CheckBox中的內容,我假設你將在你的ViewModel上定義一個Image。

 <CheckBox> 
      <CheckBox.Content> 
       <StackPanel Orientation="Horizontal"> 
        <Image Source="{Binding Image}"/> 
        <TextBlock Text="{Binding Name}"/> 
       </StackPanel> 
      </CheckBox.Content> 

選擇

最後,你必須處理項目的選擇。我建議添加一個IsSelected屬性到你的FileViewModel和FolderViewModels。對於文件來說這非常簡單,它只是一個布爾。

public class FileViewModel : INotifyProperty 
    ... 
    public bool IsSelected //Something here to handle get/set and NotifyPropertyChanged that depends on your MVVM framework, I use ReactiveUI a lot so that's this syntax 
    { 
     get { return _IsSelected;} 
     set { this.RaiseAndSetIfChanged(x=>x.IsSelected, value); } 
    } 

<CheckBox IsChecked="{Binding IsSelected}"> 

它稍微複雜與FolderViewModel,我會看看第二個邏輯。首先XAML中,只需用

<CheckBox IsThreeState="True" IsChecked="{Binding IsSelected}"> 
    <!--IsChecked = True, False or null--> 

取代目前的複選框聲明所以現在我們需要返回一組Nullable<bool>(又名bool?)。

public bool? IsSelected 
{ 
    get 
    { 
     if (SubFoldersAndFiles.All(x=>x.IsSelected) return true; 
     if (SubFoldersAndFiles.All(x=>x.IsSelected==false) return false; 
     return null; 
    } 
    set 
    { 
     // We can't set to indeterminate at folder level so we have to set to 
     // set to oposite of what we have now 
     if(value == null) 
     value = !IsSelected; 

     foreach(var x in SubFoldersAndFiles) 
      x.IsSelected = value; 
    } 

或者非常類似的東西...

+0

謝謝,我會嘗試更新以防萬一 – Ofir

0

考慮看看由@AlSki的回答後,我決定它既不直觀,也沒有我喜歡多才多藝,足以和我自己的解決方案上來。然而,使用我的解決方案的缺點是它需要多一點的樣板。另一方面,它提供了很大的靈活性。

下面的示例假設您使用.NET 4.6.1和C#6.0。

/// <summary> 
/// A base for abstract objects (implements INotifyPropertyChanged). 
/// </summary> 
[Serializable] 
public abstract class AbstractObject : INotifyPropertyChanged 
{ 
    /// <summary> 
    /// 
    /// </summary> 
    [field: NonSerialized()] 
    public event PropertyChangedEventHandler PropertyChanged; 

    /// <summary> 
    /// 
    /// </summary> 
    /// <param name="propertyName"></param> 
    public void OnPropertyChanged(string propertyName) 
    { 
     PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    /// <typeparam name="TKind"></typeparam> 
    /// <param name="Source"></param> 
    /// <param name="NewValue"></param> 
    /// <param name="Names"></param> 
    protected virtual bool SetValue<TKind>(ref TKind Source, TKind NewValue, params string[] Notify) 
    { 
     //Set value if the new value is different from the old 
     if (!Source.Equals(NewValue)) 
     { 
      Source = NewValue; 

      //Notify all applicable properties 
      Notify?.ForEach(i => OnPropertyChanged(i)); 

      return true; 
     } 

     return false; 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    public AbstractObject() 
    { 
    } 
} 

具有檢查狀態的對象。

/// <summary> 
/// Specifies an object with a checked state. 
/// </summary> 
public interface ICheckable 
{ 
    /// <summary> 
    /// 
    /// </summary> 
    bool? IsChecked 
    { 
     get; set; 
    } 
} 

/// <summary> 
/// 
/// </summary> 
public class CheckableObject : AbstractObject, ICheckable 
{ 
    /// <summary> 
    /// 
    /// </summary> 
    [field: NonSerialized()] 
    public event EventHandler<EventArgs> Checked; 

    /// <summary> 
    /// 
    /// </summary> 
    [field: NonSerialized()] 
    public event EventHandler<EventArgs> Unchecked; 

    /// <summary> 
    /// 
    /// </summary> 
    [XmlIgnore] 
    protected bool? isChecked; 
    /// <summary> 
    /// 
    /// </summary> 
    public virtual bool? IsChecked 
    { 
     get 
     { 
      return isChecked; 
     } 
     set 
     { 
      if (SetValue(ref isChecked, value, "IsChecked") && value != null) 
      { 
       if (value.Value) 
       { 
        OnChecked(); 
       } 
       else OnUnchecked(); 
      } 
     } 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    /// <returns></returns> 
    public override string ToString() 
    { 
     return base.ToString(); 
     //return isChecked.ToString(); 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    protected virtual void OnChecked() 
    { 
     Checked?.Invoke(this, new EventArgs()); 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    protected virtual void OnUnchecked() 
    { 
     Unchecked?.Invoke(this, new EventArgs()); 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    public CheckableObject() : base() 
    { 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    /// <param name="isChecked"></param> 
    public CheckableObject(bool isChecked = false) 
    { 
     IsChecked = isChecked; 
    } 
} 

用於檢查系統對象視圖模型:

/// <summary> 
/// 
/// </summary> 
public class CheckableSystemObject : CheckableObject 
{ 
    #region Properties 

    /// <summary> 
    /// 
    /// </summary> 
    public event EventHandler Collapsed; 

    /// <summary> 
    /// 
    /// </summary> 
    public event EventHandler Expanded; 

    bool StateChangeHandled = false; 

    CheckableSystemObject Parent { get; set; } = default(CheckableSystemObject); 

    ISystemProvider SystemProvider { get; set; } = default(ISystemProvider); 

    ConcurrentCollection<CheckableSystemObject> children = new ConcurrentCollection<CheckableSystemObject>(); 
    /// <summary> 
    /// 
    /// </summary> 
    public ConcurrentCollection<CheckableSystemObject> Children 
    { 
     get 
     { 
      return children; 
     } 
     private set 
     { 
      SetValue(ref children, value, "Children"); 
     } 
    } 

    bool isExpanded = false; 
    /// <summary> 
    /// 
    /// </summary> 
    public bool IsExpanded 
    { 
     get 
     { 
      return isExpanded; 
     } 
     set 
     { 
      if (SetValue(ref isExpanded, value, "IsExpanded")) 
      { 
       if (value) 
       { 
        OnExpanded(); 
       } 
       else OnCollapsed(); 
      } 
     } 
    } 

    bool isSelected = false; 
    /// <summary> 
    /// 
    /// </summary> 
    public bool IsSelected 
    { 
     get 
     { 
      return isSelected; 
     } 
     set 
     { 
      SetValue(ref isSelected, value, "IsSelected"); 
     } 
    } 

    string path = string.Empty; 
    /// <summary> 
    /// 
    /// </summary> 
    public string Path 
    { 
     get 
     { 
      return path; 
     } 
     set 
     { 
      SetValue(ref path, value, "Path"); 
     } 
    } 

    bool queryOnExpanded = false; 
    /// <summary> 
    /// 
    /// </summary> 
    public bool QueryOnExpanded 
    { 
     get 
     { 
      return queryOnExpanded; 
     } 
     set 
     { 
      SetValue(ref queryOnExpanded, value); 
     } 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    public override bool? IsChecked 
    { 
     get 
     { 
      return isChecked; 
     } 
     set 
     { 
      if (SetValue(ref isChecked, value, "IsChecked") && value != null) 
      { 
       if (value.Value) 
       { 
        OnChecked(); 
       } 
       else OnUnchecked(); 
      } 
     } 
    } 

    #endregion 

    #region CheckableSystemObject 

    /// <summary> 
    /// 
    /// </summary> 
    /// <param name="path"></param> 
    /// <param name="systemProvider"></param> 
    /// <param name="isChecked"></param> 
    public CheckableSystemObject(string path, ISystemProvider systemProvider, bool? isChecked = false) : base() 
    { 
     Path = path; 
     SystemProvider = systemProvider; 
     IsChecked = isChecked; 
    } 

    #endregion 

    #region Methods 

    void Determine() 
    { 
     //If it has a parent, determine it's state by enumerating all children, but current instance, which is already accounted for. 
     if (Parent != null) 
     { 
      StateChangeHandled = true; 
      var p = Parent; 
      while (p != null) 
      { 
       p.IsChecked = Determine(p); 
       p = p.Parent; 
      } 
      StateChangeHandled = false; 
     } 
    } 

    bool? Determine(CheckableSystemObject Root) 
    { 
     //Whether or not all children and all children's children have the same value 
     var Uniform = true; 

     //If uniform, the value 
     var Result = default(bool?); 

     var j = false; 
     foreach (var i in Root.Children) 
     { 
      //Get first child's state 
      if (j == false) 
      { 
       Result = i.IsChecked; 
       j = true; 
      } 
      //If the previous child's state is not equal to the current child's state, it is not uniform and we are done! 
      else if (Result != i.IsChecked) 
      { 
       Uniform = false; 
       break; 
      } 
     } 

     return !Uniform ? null : Result; 
    } 

    void Query(ISystemProvider SystemProvider) 
    { 
     children.Clear(); 
     if (SystemProvider != null) 
     { 
      foreach (var i in SystemProvider.Query(path)) 
      { 
       children.Add(new CheckableSystemObject(i, SystemProvider, isChecked) 
       { 
        Parent = this 
       }); 
      } 
     } 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    protected override void OnChecked() 
    { 
     base.OnChecked(); 

     if (!StateChangeHandled) 
     { 
      //By checking the root only, all children are checked automatically 
      foreach (var i in children) 
       i.IsChecked = true; 

      Determine(); 
     } 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    protected override void OnUnchecked() 
    { 
     base.OnUnchecked(); 

     if (!StateChangeHandled) 
     { 
      //By unchecking the root only, all children are unchecked automatically 
      foreach (var i in children) 
       i.IsChecked = false; 

      Determine(); 
     } 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    protected virtual void OnCollapsed() 
    { 
     Collapsed?.Invoke(this, new EventArgs()); 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    protected virtual void OnExpanded() 
    { 
     Expanded?.Invoke(this, new EventArgs()); 

     if (!children.Any<CheckableSystemObject>() || queryOnExpanded) 
      BeginQuery(SystemProvider); 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    /// <param name="SystemProvider"></param> 
    public async void BeginQuery(ISystemProvider SystemProvider) 
    { 
     await Task.Run(() => Query(SystemProvider)); 
    } 

    #endregion 
} 

實用程序用於查詢系統對象;請注意,通過定義您自己的SystemProvider,您可以查詢不同類型的系統(即本地或遠程)。默認情況下,查詢本地系統。如果您想要顯示來自FTP等遠程服務器的對象,您需要定義一個SystemProvider,它使用相應的Web協議。

/// <summary> 
/// Specifies an object capable of querying system objects. 
/// </summary> 
public interface ISystemProvider 
{ 
    /// <summary> 
    /// 
    /// </summary> 
    /// <param name="Path">The path to query.</param> 
    /// <param name="Source">A source used to make queries.</param> 
    /// <returns>A list of system object paths.</returns> 
    IEnumerable<string> Query(string Path, object Source = null); 
} 

/// <summary> 
/// Defines base functionality for an <see cref="ISystemProvider"/>. 
/// </summary> 
public abstract class SystemProvider : ISystemProvider 
{ 
    /// <summary> 
    /// 
    /// </summary> 
    /// <param name="Path"></param> 
    /// <param name="Source"></param> 
    /// <returns></returns> 
    public abstract IEnumerable<string> Query(string Path, object Source = null); 
} 

/// <summary> 
/// Defines functionality to query a local system. 
/// </summary> 
public class LocalSystemProvider : SystemProvider 
{ 
    /// <summary> 
    /// 
    /// </summary> 
    /// <param name="Path"></param> 
    /// <param name="Source"></param> 
    /// <returns></returns> 
    public override IEnumerable<string> Query(string Path, object Source = null) 
    { 
     if (Path.IsNullOrEmpty()) 
     { 
      foreach (var i in System.IO.DriveInfo.GetDrives()) 
       yield return i.Name; 
     } 
     else 
     { 
      if (System.IO.Directory.Exists(Path)) 
      { 
       foreach (var i in System.IO.Directory.EnumerateFileSystemEntries(Path)) 
        yield return i; 
      } 
     } 
    } 
} 

然後繼承TreeView,這使這一切都在一起:

/// <summary> 
/// 
/// </summary> 
public class SystemObjectPicker : TreeViewExt 
{ 
    #region Properties 

    /// <summary> 
    /// 
    /// </summary> 
    public static DependencyProperty QueryOnExpandedProperty = DependencyProperty.Register("QueryOnExpanded", typeof(bool), typeof(SystemObjectPicker), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnQueryOnExpandedChanged)); 
    /// <summary> 
    /// 
    /// </summary> 
    public bool QueryOnExpanded 
    { 
     get 
     { 
      return (bool)GetValue(QueryOnExpandedProperty); 
     } 
     set 
     { 
      SetValue(QueryOnExpandedProperty, value); 
     } 
    } 
    static void OnQueryOnExpandedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     d.As<SystemObjectPicker>().OnQueryOnExpandedChanged((bool)e.NewValue); 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    public static DependencyProperty RootProperty = DependencyProperty.Register("Root", typeof(string), typeof(SystemObjectPicker), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnRootChanged)); 
    /// <summary> 
    /// 
    /// </summary> 
    public string Root 
    { 
     get 
     { 
      return (string)GetValue(RootProperty); 
     } 
     set 
     { 
      SetValue(RootProperty, value); 
     } 
    } 
    static void OnRootChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     d.As<SystemObjectPicker>().OnRootChanged((string)e.NewValue); 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    static DependencyProperty SystemObjectsProperty = DependencyProperty.Register("SystemObjects", typeof(ConcurrentCollection<CheckableSystemObject>), typeof(SystemObjectPicker), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)); 
    /// <summary> 
    /// 
    /// </summary> 
    ConcurrentCollection<CheckableSystemObject> SystemObjects 
    { 
     get 
     { 
      return (ConcurrentCollection<CheckableSystemObject>)GetValue(SystemObjectsProperty); 
     } 
     set 
     { 
      SetValue(SystemObjectsProperty, value); 
     } 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    public static DependencyProperty SystemProviderProperty = DependencyProperty.Register("SystemProvider", typeof(ISystemProvider), typeof(SystemObjectPicker), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSystemProviderChanged)); 
    /// <summary> 
    /// 
    /// </summary> 
    public ISystemProvider SystemProvider 
    { 
     get 
     { 
      return (ISystemProvider)GetValue(SystemProviderProperty); 
     } 
     set 
     { 
      SetValue(SystemProviderProperty, value); 
     } 
    } 
    static void OnSystemProviderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     d.As<SystemObjectPicker>().OnSystemProviderChanged((ISystemProvider)e.NewValue); 
    } 

    #endregion 

    #region SystemObjectPicker 

    /// <summary> 
    /// 
    /// </summary> 
    public SystemObjectPicker() : base() 
    { 
     SetCurrentValue(SystemObjectsProperty, new ConcurrentCollection<CheckableSystemObject>()); 
     SetCurrentValue(SystemProviderProperty, new LocalSystemProvider()); 

     SetBinding(ItemsSourceProperty, new Binding() 
     { 
      Mode = BindingMode.OneWay, 
      Path = new PropertyPath("SystemObjects"), 
      Source = this 
     }); 
    } 

    #endregion 

    #region Methods 

    void OnQueryOnExpandedChanged(CheckableSystemObject Item, bool Value) 
    { 
     foreach (var i in Item.Children) 
     { 
      i.QueryOnExpanded = Value; 
      OnQueryOnExpandedChanged(i, Value); 
     } 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    /// <param name="Value"></param> 
    protected virtual void OnQueryOnExpandedChanged(bool Value) 
    { 
     foreach (var i in SystemObjects) 
      OnQueryOnExpandedChanged(i, Value); 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    /// <param name="Provider"></param> 
    /// <param name="Root"></param> 
    protected virtual void OnRefreshed(ISystemProvider Provider, string Root) 
    { 
     SystemObjects.Clear(); 
     if (Provider != null) 
     { 
      foreach (var i in Provider.Query(Root)) 
      { 
       SystemObjects.Add(new CheckableSystemObject(i, SystemProvider) 
       { 
        QueryOnExpanded = QueryOnExpanded 
       }); 
      } 
     } 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    /// <param name="Value"></param> 
    protected virtual void OnRootChanged(string Value) 
    { 
     OnRefreshed(SystemProvider, Value); 
    } 

    /// <summary> 
    /// 
    /// </summary> 
    /// <param name="Value"></param> 
    protected virtual void OnSystemProviderChanged(ISystemProvider Value) 
    { 
     OnRefreshed(Value, Root); 
    } 

    #endregion 
} 

顯然,這是大大低於@ AlSki的答案比較複雜,但同樣,你會得到更多的靈活性和硬的東西是已經爲你照顧。此外,如果你感興趣的話,我已經發布了這個代碼的最新版本my open source project(3.1);如果沒有,上面的樣品就是你需要的。

如果你不下載該項目,請注意以下事項:

  • 你會發現一些擴展方法是不存在的,這可以補充他們的同行(如IsNullOrEmpty擴展是相同的string.IsNullOrEmpty()) 。
  • TreeViewExt是一個自定義TreeView我設計的,所以如果你不在乎這一點,只需將TreeViewExt更改爲TreeView;無論哪種方式,您都不必爲其定義專門的控制模板,因爲它旨在與TreeView的現有設施配合使用。
  • 在示例中,我使用了我自己的版本ObservableCollection;這樣你就可以在後臺線程上查詢數據而不會跳過箍環。將其更改爲ObservableCollection並使所有查詢同步或使用您自己的併發ObservableCollection來保留異步功能。

最後,這裏是你將如何使用控制:

<Controls.Extended:SystemObjectPicker> 
    <Controls.Extended:SystemObjectPicker.ItemContainerStyle> 
     <Style TargetType="TreeViewItem" BasedOn="{StaticResource {x:Type TreeViewItem}}"> 
      <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/> 
     </Style> 
    </Controls.Extended:SystemObjectPicker.ItemContainerStyle> 
    <Controls.Extended:SystemObjectPicker.ItemTemplate> 
     <HierarchicalDataTemplate ItemsSource="{Binding Children, Mode=OneWay}"> 
      <StackPanel Orientation="Horizontal"> 
       <CheckBox 
        IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
        Margin="0,0,5,0"/> 
       <TextBlock 
        Text="{Binding Path, Converter={StaticResource FileNameConverter}, Mode=OneWay}"/> 
      </StackPanel> 
     </HierarchicalDataTemplate> 
    </Controls.Extended:SystemObjectPicker.ItemTemplate> 
</Controls.Extended:SystemObjectPicker> 

待辦事項

  • 屬性添加到CheckableSystemObject暴露於系統對象視圖模型;這樣,您就可以訪問與其路徑或其他數據源相關的FileInfo/DirectoryInfo。如果對象是遠程的,你可能已經定義了自己的類來描述它,如果你有它的參考,這可能是有用的。
  • 查詢本地系統時可能出現異常;如果系統對象無法訪問,則會失敗。 LocalSystemProvider也無法解決超過260個字符的系統路徑;然而,這超出了這個項目的範圍。

注意要版主

我引用我自己的開源項目,爲方便起見,我發表在最新版本的上述樣品;我的意圖不是自我宣傳,所以如果引用你自己的項目時,我會繼續刪除鏈接。