我是初學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