1

我是初学者 WPF。我在工作中开发了一个新项目,我需要插入一个具有多项选择的文件资源管理器控件。

该概念需要类似于 acronis 文件资源管理器:(带有复选框的 Treeview)

查看示例

看看左边的容器,我需要实现类似的东西,我通过谷歌搜索了很多,我看到了很多实现,但没有什么与这个不相似。

因为我在 WPF 方面没有太多经验,所以我很难开始。

你有一些技巧或类似的项目可以帮助我吗?

我的项目基于 MVVM DP。

谢谢

4

2 回答 2

5

重塑 Treeview 非常简单,您可以从要绑定的集合开始,即

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

但是,您随后需要定义如何显示已绑定的数据。我假设您的项目只是 FolderViewModels 和 FileViewModels(都具有 Name 属性)的 IEnumerable(任何列表或数组),所以现在我们需要说明如何显示它们。你可以通过定义一个 DataTemplate 来做到这一点,因为这是一棵树,我们使用一个 HeirarchicalDataTemplate 因为它也定义了 subItems

<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>

图标 如果要显示图标,则更改 CheckBox 中的内容,我假设您将在 ViewModel 上定义图像。

        <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 声明替换为

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

所以现在我们需要返回一组Nullable<bool>(aka 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;
   }

或者非常相似的东西...

于 2012-09-27T15:41:04.080 回答
1

在查看@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 的答案要复杂得多,但是,您再次获得了更大的灵活性,并且已经为您处理了困难的事情。

另外,如果你对这样的事情感兴趣,我已经在我的开源项目(3.1)的最新版本中发布了这段代码;如果没有,上面的示例就是让它工作所需的全部内容。

如果您不下载该项目,请注意以下事项:

  • 您会发现一些不存在的扩展方法,可以用它们的对应物来补充(例如,IsNullOrEmpty扩展与 相同string.IsNullOrEmpty())。
  • TreeViewExtTreeView我设计的自定义,所以如果您不关心,只需更改TreeViewExtTreeView; 无论哪种方式,您都不必为它定义一个特殊的控制模板,因为它是为与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 个字符的系统路径;但是,这超出了本项目的范围。

版主须知

为方便起见,我引用了自己的开源项目,因为我在最新版本中发布了上述示例;我的目的不是自我推销,所以如果不赞成引用您自己的项目,我将继续删除链接。

于 2017-02-17T08:06:19.183 回答