The heart of the dialog is WPF TreeView control and underlying data model. Here is the model:
public class TreeItem : NotifiableObject
{
#region Properties
public bool IsFullyLoaded { get; set; }
public string Name
{
get
{
return name;
}
set
{
name = value;
NotifyPropertyChanged(() => Name);
}
}
public TreeItem Parent
{
get
{
return parent;
}
set
{
parent = value;
NotifyPropertyChanged(() => Parent);
}
}
public ObservableCollection Childs
{
get
{
return childs;
}
set
{
childs = value;
NotifyPropertyChanged(() => Childs);
}
}
#endregion
public TreeItem(string name, TreeItem parent)
{
Name = name;
IsFullyLoaded = false;
Parent = parent;
Childs = new ObservableCollection();
}
public string GetFullPath()
{
Stack stack = new Stack();
var ti = this;
while (ti.Parent != null)
{
stack.Push(ti.Name);
ti = ti.Parent;
}
string path = stack.Pop();
while (stack.Count > 0)
{
path = Path.Combine(path, stack.Pop());
}
return path;
}
#region Private fields
private string name;
private TreeItem parent;
private ObservableCollection childs;
#endregion
}
public class DriveTreeItem : TreeItem
{
public DriveType DriveType { get; set; }
public DriveTreeItem(string name, DriveType driveType, TreeItem parent)
: base(name, parent)
{
DriveType = driveType;
}
}
And here is the control itself:
public partial class FolderPickerControl : UserControl, INotifyPropertyChanged
{
#region Constants
private static readonly string EmptyItemName = "Empty";
#endregion
#region Properties
public TreeItem Root
{
get
{
return root;
}
private set
{
root = value;
NotifyPropertyChanged(() => Root);
}
}
public TreeItem SelectedItem
{
get
{
return selectedItem;
}
private set
{
selectedItem = value;
NotifyPropertyChanged(() => SelectedItem);
}
}
public string SelectedPath { get; private set; }
#endregion
public FolderPickerControl()
{
InitializeComponent();
Init();
}
#region INotifyPropertyChanged Members
public void NotifyPropertyChanged(Expression> property)
{
var lambda = (LambdaExpression)property;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = (UnaryExpression)lambda.Body;
memberExpression = (MemberExpression)unaryExpression.Operand;
}
else memberExpression = (MemberExpression)lambda.Body;
OnPropertyChanged(memberExpression.Member.Name);
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region Private methods
private void Init()
{
root = new TreeItem("root", null);
var systemDrives = DriveInfo.GetDrives();
foreach (var sd in systemDrives)
{
var item = new DriveTreeItem(sd.Name, sd.DriveType, root);
item.Childs.Add(new TreeItem(EmptyItemName, item));
root.Childs.Add(item);
}
Root = root; // to notify UI
}
private void TreeView_Selected(object sender, RoutedEventArgs e)
{
var tvi = e.OriginalSource as TreeViewItem;
if (tvi != null)
{
SelectedItem = tvi.DataContext as TreeItem;
SelectedPath = SelectedItem.GetFullPath();
}
}
private void TreeView_Expanded(object sender, RoutedEventArgs e)
{
var tvi = e.OriginalSource as TreeViewItem;
var treeItem = tvi.DataContext as TreeItem;
if (treeItem != null)
{
if (!treeItem.IsFullyLoaded)
{
treeItem.Childs.Clear();
string path = treeItem.GetFullPath();
DirectoryInfo dir = new DirectoryInfo(path);
try
{
var subDirs = dir.GetDirectories();
foreach (var sd in subDirs)
{
TreeItem item = new TreeItem(sd.Name, treeItem);
item.Childs.Add(new TreeItem(EmptyItemName, item));
treeItem.Childs.Add(item);
}
}
catch { }
treeItem.IsFullyLoaded = true;
}
}
else
throw new Exception();
}
#endregion
#region Private fields
private TreeItem root;
private TreeItem selectedItem;
#endregion
}
More code can be found here. As you can see stub childs are added for each tree item if it hasn’t been expanded yet. Lazy loading is used, so actual childs are fetched only when user expands folder or drive.
Pages: 1 2
March 22, 2011 at 5:49 am
is it free ? no license ?
March 22, 2011 at 1:45 pm
Yep it’s under GNU GPL v3 so you can use it both for open source and commercial software. Feel free to modify for your needs or ask for features
April 3, 2011 at 1:34 am
Tillas,
Thanks for pointing me to this on my blog. However, I cannot use it because it is GPL. You statement in the comment above is not accurate.
Actually the GPL means you CANNOT use it for free in Commercial software.
If you want it to be free to use in Commercial software, you need to use a more permissive BSD license.
I have post about these license differences.
Differences between the BSD/FreeBSD Copyrights and the GNU Public License (GPL)
Reading your comment, it seems like you intended it to be a free license that can be used for commerically. If that is your intent, you must change the license to be a more permissive license. BSD, MIT, etc…
April 3, 2011 at 8:26 am
Thanks for you clarification. I will update licensing ASAP.
December 21, 2011 at 11:16 pm
Jared, that is not really correct. You can use GPL for free in Commercial Software. It only means that that commercial software automaticly also gets the GPL license and the GPL license also applies to that software. A direct comment from the GPL license site:
Does the GPL allow me to sell copies of the program for money? (#DoesTheGPLAllowMoney)
- Yes, the GPL allows everyone to do this. The right to sell copies is part of the definition of free software. Except in one special situation, there is no limit on what price you can charge. (The one exception is the required written offer to provide source code that must accompany binary-only release.)
Source: http://www.gnu.org/licenses/gpl-faq.html#DoesTheGPLAllowMoney
March 23, 2011 at 8:34 pm
Nice work! I just have one issue (maybe the problem is on my side ;-)): When i choose to “Cancel” the folder select dialog and then try to open it again – i.e. hit the ‘Browse…’ – Button again – i get an InvalidOperationException when i execute dlg.ShowDialog():
“DialogResult can be set only after Window is created and shown as dialog.”
Any idea?
March 23, 2011 at 9:01 pm
I think you use dialog.Show() instead if dialog.ShowDialog(). Can you show me the usage code please?
March 23, 2011 at 11:16 pm
Sure, here’s the code snippet:
private void btnSelInDir_Click(object sender, RoutedEventArgs e) {
FolderPickerDialog dlg = new FolderPickerDialog();
//dlg.Title = “Select Input Directory”;
if (dlg.ShowDialog() == true) {
txfInputDir.Text = dlg.SelectedPath;
}
}
Do i need to do some cleanup/dispose?
March 23, 2011 at 11:48 pm
Very strange. I’ve added following internal check while closing dialog:
private void CancelButton_Click(object sender, RoutedEventArgs e)
{
if (ComponentDispatcher.IsThreadModal)
{
DialogResult = false;
}
else
{
Close();
}
}
This is done according to http://stackoverflow.com/questions/1378602/setting-dialogresult-only-after-showdialog-in-wpf
Please download new binray package from google.code (links on the first page are updated) and let me know wheter it helps or not. Thanks!
March 24, 2011 at 12:04 pm
The new version solved the problem. Thank you!
April 11, 2011 at 12:30 pm
Hi,
I think there’s a problem with your read-only SVN checkout link as I cannot get it to work.
Thanks
John
May 6, 2011 at 3:47 pm
Hello,
First thanks for providing this solution.
Is it possible to set a Startup value for the SelectedPath prior to opening the Dialog? This Property is read-only (r133).
Thanks
Maria
May 6, 2011 at 10:13 pm
Hello Maria! Thanks for request! I’ve created issue http://code.google.com/p/wmediacatalog/issues/detail?id=38 and will fix it in several two days. You can monitor status of your issue using link given above.
I will provide additional InitialPath property with public setter which will allow you to start dialog with expected initial folder expanded. Though I still suggest to leave SelectedPath read-only to avoid confusion.
May 21, 2011 at 11:46 pm
Hello is the multiple selected folders features are there for this lib?
May 27, 2011 at 1:49 pm
Hi Tillias,
very good works thanks for providing it!
I am interested on the improvement identified by Maria, and I see it is fixed, can you tell me when it will be available to download?
Thanks
Rossano
May 27, 2011 at 5:46 pm
I’ve updated binary assembly according to changes. Feel free to try it out
May 27, 2011 at 9:38 pm
Hi Tillias,
Thanks for this! Nice work.
Tim
May 31, 2011 at 1:06 pm
Very nice.
I too am worried about the GPL. It still says GPL on Google Code.
Also, one feature that would be great is a new folder button.
If the license changes, I may try to add some features to it, because I would use it in my projects.
May 31, 2011 at 6:37 pm
It makes sense to change licensing model for FolderPicker to BSD license. I’ve created issue for new folder functionality
September 5, 2011 at 6:26 am
Is there a way you can sign the posted assembly so it can be used in projects that are signed? Without this I can’t use the dll directly.
November 11, 2011 at 10:26 pm
How about creating a NuGet package for this? Thanks!
December 14, 2011 at 7:58 am
Sure, no prob. I’ll post it this week
December 13, 2011 at 11:13 pm
I’m in the process of modifying FolderPickerControl. I made InitialPath a DependencyProperty so I could set it via binding. I’ve also ran through the code with ReSharper and made the usual suggested changes (and Childs is now Children). Next step is to code it such that the InitialPath can be the root. What I’m not understanding is the use of Tasks in the initial update to the UI. From what I can see from setting a breakpoint inside the ContinueWith, you’re waiting for the tree to become visible before painting it. Is that correct? I put the tree in an Expander, so the ContinueWith block isn’t called until the Expander is expanded. Why wait? You’ve already loaded the initial folders under the drive. Thanks!
December 14, 2011 at 8:00 am
Tasks are needed to handle container generation stuff. Without them we can’t implement lazy loading. Feel free to commit changes — I’ll provide you with google.code credentials — just send me your google e-mail
December 14, 2011 at 7:33 pm
I understand lazy loading but why this and not just hook the Expanded event? Also, have you considered using Reactive Framework in lieu of Tasks? Also, have you thought about moving to GitHub so I can fork the project? Thanks!
December 14, 2011 at 8:03 pm
davidbitton:
Have you tested it with Expanded event? Does it work? I used Tasks to wait until ContainerGenerator finished generating treeView items. What is the advantage of Reactive Framework? I think it should be as simple and lightweight as possible by the means of external dependencies.
And yes — gitHub is nice place to store it. Though we should think about default MS icons and licensing in case of moving to gitHub. Can you please advise about it?
December 14, 2011 at 9:41 pm
As far as icons go, I’ve used Win32 calls for that. I use SHGetFileInfo(…). Now using p/invoke precludes the lib’s use in mono. Not sure if that matters. Regarding Reactive Framework, I find that it makes subscribing to asynch operation very easy. Sure, the lib would then have a dependence on another lib, but if coders rely on NuGet, then it just becomes a dependency and Rx is loaded as necessary. Email me so we can discuss further.