我是初学WPF。我在我的工作中开发一个新项目,我需要插入一个具有多个选择的文件浏览器控件。基于多选树视图实现文件浏览器
概念需要类似的Acronis文件浏览器(与复选框树视图)
看一下左边的容器,我需要实现与此类似, 我HABE搜索了很多通过谷歌和我看到了很多的实现,但没有什么不是这样的。
因为我没有很多WPF经验,所以我很难开始。
你有一些提示或类似的项目可能会帮助我做到吗?
我的项目基于MVVM DP。
感谢
我是初学WPF。我在我的工作中开发一个新项目,我需要插入一个具有多个选择的文件浏览器控件。基于多选树视图实现文件浏览器
概念需要类似的Acronis文件浏览器(与复选框树视图)
看一下左边的容器,我需要实现与此类似, 我HABE搜索了很多通过谷歌和我看到了很多的实现,但没有什么不是这样的。
因为我没有很多WPF经验,所以我很难开始。
你有一些提示或类似的项目可能会帮助我做到吗?
我的项目基于MVVM DP。
感谢
重塑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;
}
或者非常类似的东西...
考虑看看由@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个字符的系统路径;然而,这超出了这个项目的范围。注意要版主
我引用我自己的开源项目,为方便起见,我发表在最新版本的上述样品;我的意图不是自我宣传,所以如果引用你自己的项目时,我会继续删除链接。
谢谢,我会尝试更新以防万一 – Ofir