2012-09-27 54 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个字符的系统路径;然而,这超出了这个项目的范围。

注意要版主

我引用我自己的开源项目,为方便起见,我发表在最新版本的上述样品;我的意图不是自我宣传,所以如果引用你自己的项目时,我会继续删除链接。