I develop a WPF4 application and in my app I need to let the user select a folder where the application will store something (files, generated reports etc.).
My requirements:
Ability to see the standard folder tree
Ability to select a folder
WPF look & feel, this dialog must look like part of a modern application designed for Windows Vista/7 and not Windows 2000 or even Win9x.
As I understand, until 2010 (.Net 4.0) there won't be a standard folder dialog, but maybe there are some changes in version 4.0?
Or the only thing I can do, is to use an old-school WinForms dialog? If it's the only way to do what I need, how can I make it looking closer to Vista/7 style and not Win9x?
Windows Presentation Foundation 4.5 Cookbook by Pavel Yosifovich on page 155 in the section on "Using the common dialog boxes" says:
"What about folder selection (instead of files)? The WPF
OpenFileDialog does not support that. One solution is to use Windows
Forms' FolderBrowseDialog class. Another good solution is to use the
Windows API Code Pack described shortly."
I downloaded the API Code Pack from Windows® API Code Pack for Microsoft® .NET Framework Windows API Code Pack: Where is it?, then added references to Microsoft.WindowsAPICodePack.dll and Microsoft.WindowsAPICodePack.Shell.dll to my WPF 4.5 project.
Example:
using Microsoft.WindowsAPICodePack.Dialogs;
var dlg = new CommonOpenFileDialog();
dlg.Title = "My Title";
dlg.IsFolderPicker = true;
dlg.InitialDirectory = currentDirectory;
dlg.AddToMostRecentlyUsedList = false;
dlg.AllowNonFileSystemItems = false;
dlg.DefaultDirectory = currentDirectory;
dlg.EnsureFileExists = true;
dlg.EnsurePathExists = true;
dlg.EnsureReadOnly = false;
dlg.EnsureValidNames = true;
dlg.Multiselect = false;
dlg.ShowPlacesList = true;
if (dlg.ShowDialog() == CommonFileDialogResult.Ok)
{
var folder = dlg.FileName;
// Do something with selected folder string
}
I wrote about it on my blog a long time ago, WPF's support for common file dialogs is really bad (or at least is was in 3.5 I didn't check in version 4), but it's easy to work around it.
You need to add the correct manifest to your application, that will give you a modern style message boxes and folder browser (WinForms, FolderBrowserDialog) but not WPF file open/save dialogs, this is described in those 3 posts (if you don't care about the explanation and only want the solution go directly to the 3rd):
Why am I Getting Old Style File Dialogs and Message Boxes with WPF
Will Setting a Manifest Solve My WPF Message Box Style Problems?
The Application Manifest Needed for XP and Vista Style File Dialogs and Message Boxes with WPF
Fortunately, the open/save dialogs are very thin wrappers around the Win32 API that is easy to call with the right flags to get the Vista/7 style (after setting the manifest)
Vista style open and save dialogs with WPF (without using the Vista bridge sample)
Add The Windows API Code Pack-Shell to your project
using Microsoft.WindowsAPICodePack.Dialogs;
...
var dialog = new CommonOpenFileDialog();
dialog.IsFolderPicker = true;
CommonFileDialogResult result = dialog.ShowDialog();
If you don't want to use Windows Forms nor edit manifest files, I came up with a very simple hack using WPF's SaveAs dialog for actually selecting a directory.
No using directive needed, you may simply copy-paste the code below !
It should still be very user-friendly and most people will never notice.
The idea comes from the fact that we can change the title of that dialog, hide files, and work around the resulting filename quite easily.
It is a big hack for sure, but maybe it will do the job just fine for your usage...
In this example I have a textbox object to contain the resulting path, but you may remove the related lines and use a return value if you wish...
// Create a "Save As" dialog for selecting a directory (HACK)
var dialog = new Microsoft.Win32.SaveFileDialog();
dialog.InitialDirectory = textbox.Text; // Use current value for initial dir
dialog.Title = "Select a Directory"; // instead of default "Save As"
dialog.Filter = "Directory|*.this.directory"; // Prevents displaying files
dialog.FileName = "select"; // Filename will then be "select.this.directory"
if (dialog.ShowDialog() == true) {
string path = dialog.FileName;
// Remove fake filename from resulting path
path = path.Replace("\\select.this.directory", "");
path = path.Replace(".this.directory", "");
// If user has changed the filename, create the new directory
if (!System.IO.Directory.Exists(path)) {
System.IO.Directory.CreateDirectory(path);
}
// Our final value is in path
textbox.Text = path;
}
The only issues with this hack are :
Acknowledge button still says "Save" instead of something like "Select directory", but in a case like mines I "Save" the directory selection so it still works...
Input field still says "File name" instead of "Directory name", but we can say that a directory is a type of file...
There is still a "Save as type" dropdown, but its value says "Directory (*.this.directory)", and the user cannot change it for something else, works for me...
Most people won't notice these, although I would definitely prefer using an official WPF way if microsoft would get their heads out of their asses, but until they do, that's my temporary fix.
The FolderBrowserDialog class from System.Windows.Forms is the recommended way to display a dialog that allows a user to select a folder.
Until recently, the appearance and behaviour of this dialog was not in keeping with the other file system dialogs, which is one of the reasons why people were reluctant to use it.
The good news is that FolderBrowserDialog was "modernized" in NET Core 3.0, so is now a viable option for those writing either Windows Forms or WPF apps targeting that version or later.
In .NET Core 3.0, Windows Forms users [sic] a newer COM-based control that was introduced in Windows Vista:
To reference System.Windows.Forms in a NET Core WPF app, it is necessary to edit the project file and add the following line:
<UseWindowsForms>true</UseWindowsForms>
This can be placed directly after the existing <UseWPF> element.
Then it's just a case of using the dialog:
using System;
using System.Windows.Forms;
...
using var dialog = new FolderBrowserDialog
{
Description = "Time to select a folder",
UseDescriptionForTitle = true,
SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory)
+ Path.DirectorySeparatorChar,
ShowNewFolderButton = true
};
if (dialog.ShowDialog() == DialogResult.OK)
{
...
}
FolderBrowserDialog has a RootFolder property that supposedly "sets the root folder where the browsing starts from" but whatever I set this to it didn't make any difference; SelectedPath seemed to be the better property to use for this purpose, however the trailing backslash is required.
Also, the ShowNewFolderButton property seems to be ignored as well, the button is always shown regardless.
Microsoft.Win32.OpenFileDialog is the standard dialog that any application on Windows uses. Your user won't be surprised by its appearance when you use WPF in .NET 4.0
The dialog was altered in Vista. WPF in .NET 3.0 and 3.5 still used the legacy dialog but that was fixed in .NET 4.0. I can only guess that you started this thread because you are seeing that old dialog. Which probably means you're actually running a program that is targeting 3.5. Yes, the Winforms wrapper did get the upgrade and shows the Vista version. System.Windows.Forms.OpenFileDialog class, you'll need to add a reference to System.Windows.Forms.
MVVM + WinForms FolderBrowserDialog as behavior
public class FolderDialogBehavior : Behavior<Button>
{
public string SetterName { get; set; }
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Click += OnClick;
}
protected override void OnDetaching()
{
AssociatedObject.Click -= OnClick;
}
private void OnClick(object sender, RoutedEventArgs e)
{
var dialog = new FolderBrowserDialog();
var result = dialog.ShowDialog();
if (result == DialogResult.OK && AssociatedObject.DataContext != null)
{
var propertyInfo = AssociatedObject.DataContext.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(p => p.CanRead && p.CanWrite)
.Where(p => p.Name.Equals(SetterName))
.First();
propertyInfo.SetValue(AssociatedObject.DataContext, dialog.SelectedPath, null);
}
}
}
Usage
<Button Grid.Column="3" Content="...">
<Interactivity:Interaction.Behaviors>
<Behavior:FolderDialogBehavior SetterName="SomeFolderPathPropertyName"/>
</Interactivity:Interaction.Behaviors>
</Button>
Blogpost: http://kostylizm.blogspot.ru/2014/03/wpf-mvvm-and-winforms-folder-dialog-how.html
Based on Oyun's answer, it's better to use a dependency property for the FolderName. This allows (for example) binding to sub-properties, which doesn't work in the original. Also, in my adjusted version, the dialog shows selects the initial folder.
Usage in XAML:
<Button Content="...">
<i:Interaction.Behaviors>
<Behavior:FolderDialogBehavior FolderName="{Binding FolderPathPropertyName, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
</Button>
Code:
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interactivity;
using Button = System.Windows.Controls.Button;
public class FolderDialogBehavior : Behavior<Button>
{
#region Attached Behavior wiring
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Click += OnClick;
}
protected override void OnDetaching()
{
AssociatedObject.Click -= OnClick;
base.OnDetaching();
}
#endregion
#region FolderName Dependency Property
public static readonly DependencyProperty FolderName =
DependencyProperty.RegisterAttached("FolderName",
typeof(string), typeof(FolderDialogBehavior));
public static string GetFolderName(DependencyObject obj)
{
return (string)obj.GetValue(FolderName);
}
public static void SetFolderName(DependencyObject obj, string value)
{
obj.SetValue(FolderName, value);
}
#endregion
private void OnClick(object sender, RoutedEventArgs e)
{
var dialog = new FolderBrowserDialog();
var currentPath = GetValue(FolderName) as string;
dialog.SelectedPath = currentPath;
var result = dialog.ShowDialog();
if (result == DialogResult.OK)
{
SetValue(FolderName, dialog.SelectedPath);
}
}
}
The Ookii Dialogs for WPF has a VistaFolderBrowserDialog class that provides a complete implementation of a folder browser dialog for WPF.
https://github.com/augustoproiete/ookii-dialogs-wpf
There's also a version that works with Windows Forms.
Only such dialog is FileDialog. Its part of WinForms, but its actually only wrapper around WinAPI standard OS file dialog. And I don't think it is ugly, its actually part of OS, so it looks like OS it is run on.
Other way, there is nothing to help you with. You either need to look for 3rd party implementation, either free (and I don't think there are any good) or paid.
Just to say one thing, WindowsAPICodePack can not open CommonOpenFileDialog on Windows 7 6.1.7600.
A comment on the original question from C. Augusto Proiete suggested Ookii dialogs (https://github.com/ookii-dialogs/ookii-dialogs-wpf). That's what I ended up using in my situation. Here's how I used it in my app.
var dialog = new VistaFolderBrowserDialog()
{
Description = "Select Folder",
RootFolder = Environment.SpecialFolder.Desktop,
ShowNewFolderButton = true,
UseDescriptionForTitle = true
};
var result = dialog.ShowDialog();
if (result.HasValue && result.Value)
{
_mySelectedFolder = dialog.SelectedPath;
}
Related
I am moving some old code up to .Net6 from very old versions and am finding the new project references hard to set up.
I am trying to make a dll that has some knowledge of windows forms using C# in VScommunity 2022 and .net6.0.
I picked the C# WPF Class library as the default project to build from.
I am able to get the DLL to talk to my exe for basic operations but I am unable to get it to compile when it has references to things like MessageBox and DialogResult in it.
I cannot find any way to get VS to recognise these references. It wont let me add using System.Windows.Forms; (error CS0234 Forms does not exist in Windows) and I cant see any way to add via project properties.
What am I missing ?
Here is an example of what I am trying to compile
public class JFIO
{
public static StreamReader? OpenReader(string filepath, bool bQuiet)
{
StreamReader sr = null;
if (!File.Exists(filepath))
{
if (!bQuiet)
MessageBox.Show("Input file not found\n" + filepath);
return sr;
}
DialogResult r = DialogResult.OK;
while (r != DialogResult.Cancel)
{
try
{
sr = new StreamReader(filepath);
break;
}
catch (Exception e)
{
r = MessageBox.Show("Error opening file\n" + filepath + "\n" + e.ToString(), "", MessageBoxButtons.RetryCancel);
}
}
return sr;
}
}
Thanks for the input. This is just a simple dll that I want to use in several of my forms based EXE programs to manage file opening, IO etc.
I found that if, instead of the WPF Class Library, I use the WindowsFormControl Library as a root for the DLL I am able to access the MessageBox etc OK and achieve what I want but it will only allow the .Net framework 4.7.2, no 5.0 offered, Why is this ?
it doesn't answer your question or only partially, but i am also porting old .net code and couldn't understand how to check for dialog result in a wpf .net 6 application.
in doing so i found this:
DialogBoxWithResult dialogBoxWithResult = new DialogBoxWithResult();
// Open dialog box and retrieve dialog result when ShowDialog returns
bool? dialogResult = dialogBoxWithResult.ShowDialog();
switch (dialogResult)
{
case true:
// User accepted dialog box
break;
case false:
// User canceled dialog box
break;
default:
// Indeterminate
break;
}
so instead of checking for DialogResult.OK we now how to check for true/false ...
good luck, Chris
In your C# project go to Properties and under Application--> General you can set Enable Windows Forms for this project.
If you do so, you can only distribute to Windows computers as before (not platform independent).
Solution: I had to click "Unblock" in the file properties dialog in Windows Explorer.
I made an app in c# because i thought it will work on all windows versions. Today i was at my fathers place and downloaded my precompiled app from github. It was running but totally not as expected! Highlighted rows in a gridview wasnt visible, scollbars on a tabcontrol wasnt working and some other more or less small bugs. I had no time to download visual studio to recompile it but is this usual behavior? My father is running on windows 7 and i saw .NET framework 4.5 was installed - i compiled my app using v4.0 so i thought there should be no compatibility issues. I also created a Windows10 virtual machine at home after that and recognized it was the same behavior. I know highlighted rows or scrollbars are not that important but i also have a plugin system to load and reflect additional libraries and thats a basic feature of my app. And the assembly loader will NOT work either - just on the machine i have compiled on.
Should i compile it on multiple versions to support any "unknown" user and its operationg system? I saw that microsoft say i can determine the users version using https://learn.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed but i can do it after my app is installed so should i create some downloader to download the correct version after installation or how is this meant?
Edit i uploaded a video on youtube to show how it should work and how it accuaally works.. https://www.youtube.com/watch?v=DTQa8WlECa8
Edit I got an error message stating (translated from native german) "It was tried to load an assembly from a network address" but i dont understand this message, i never do so.. I load assemblies using
private void m_btnRegisterModule_Click(object objectSender, EventArgs eventArgs)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Multiselect = true;
openFileDialog.Title = "Register Stack";
openFileDialog.Filter = "Assemblies (*.exe, *.dll)|*.exe;*.dll|All Files (*.*)|*.*";
if (openFileDialog.ShowDialog(this) == DialogResult.Cancel) return;
Assembly assembly = null;
foreach (String file in openFileDialog.FileNames)
{
try
{
assembly = System.Reflection.Assembly.LoadFile(file);
} catch (Exception) { return; }
Type[] arrayTypes = assembly.GetExportedTypes();
foreach (Type type in arrayTypes)
{
if (!typeof(ScriptStack.Runtime.Stack).IsAssignableFrom(type)) continue;
ConstructorInfo constructorInfo = null;
try
{
constructorInfo = type.GetConstructor(new Type[0]);
}
catch (Exception) { continue; }
try
{
object objectHostModule = constructorInfo.Invoke(new object[0]);
ScriptStack.Runtime.Stack hostModule = (ScriptStack.Runtime.Stack)objectHostModule;
m_scriptManager.RegisterHostStack(hostModule);
}
catch (Exception) { continue; }
}
}
UpdateModuleControl();
pluginFilter.Select();
}
I found .net local assembly load failed with CAS policy and try to load it.. otherwise. lets see how. For now i created a messagebox to see the exact exception.
Edit Solution: I had to click "Unblock" in the file properties dialog in Windows Explorer.
Clicking "Unblock" in the file properties dialog in Windows Explorer solved my issue in some way. By "in some way" i mean i cant expect everybody to know this so someone will just think its not working crap and drop the app.. i guess
I have a textblock (ContentTextBlock) with AutomationProperties.LiveSettings="Assertive". I'm just testing and checking how useful this feature is. And... am disappointed so far.
private void Button_Click(object sender, RoutedEventArgs e)
{
ContentTextBlock.Text += " test";
var peer = UIElementAutomationPeer.FromElement(ContentTextBlock);
if(peer == null)
{
peer = UIElementAutomationPeer.CreatePeerForElement(ContentTextBlock);
peer.RaiseAutomationEvent(AutomationEvents.LiveRegionChanged);
}
peer.RaiseAutomationEvent(AutomationEvents.LiveRegionChanged);
}
When using Narrator, this works as advertised. Whenever clicking the button, Narrator announces the TextBlock text ("test" "test test" "test test test")... But when I use NVDA or JAWS, nothing happens, although the screenreader versions are relatively up-to-date. Did they really not add any support for live-regions or am I just missing an important point?
Whilst I didn't manage to get live regions to work, I found another workaround:
Tolk by Davy Kager
Tolk is a library which can, among oterrs
Detect which supported screen reader, if any, is running
Pass strings to the screen reader's speech engine and braille.
Also has support for SAPI
To include Tolk in your C# project, download it from the link above, then include tolk.cs (from src/dotnet) in your project, and place tolk.dll (it's found in bin) in the folder with your executable (or somewhere in the PATH variable). Make sure that the dll version matches your CPU target (x86/x64). Do the same for the dlls in the lib directory. Then you can use it according to the code found in the examples folder.
PS. Tolk works on Win 7 as well, so that's a bonus. The live-regions of WPF were only supported from Win 8 on.
I'm using VS 2017 and coding in C#. I installed the 4 Vlc libraries to play videos in a Windows Form Application. I put a Vlc control in the form. And then, in the code, I wrote:
vlcControl1.SetMedia(curFolder + #"\media\1.mp4");
vlcControl1.Play();
When I run it, I get a "VlcLibDirectory not found". What I need to do? I see that I can set that directory through visual controls, in the VlcControl1 properties, but what is that folder?
I'm sorry this is late...
You got the first part, getting the packages in Visual Studio, now you need the libraries for it.
Download this: https://github.com/ZeBobo5/Vlc.DotNet/tree/master
Put the lib directory somewhere the application can find it, and set that VlcLibDirectory equal to a new DirectoryInfo(path to dir).
I did it like this:
var libDirectory = new DirectoryInfo(Path.Combine(".", "libvlc", IntPtr.Size == 4 ? "x86" : "x64"));
vlcControl1 = new Vlc.DotNet.Forms.VlcControl();
vlcControl1.VlcLibDirectory = libDirectory;
The library that it needs to be loaded is libvlc.dll that is found in the folder where is installed the VLC software.
I visited practically every Google result page for this, almost lost hope, but this worked for me in the end:
1) Created an object in my FormsApp file:
VlcControl vlcControl1 = new VlcControl();
2) Instantiated it in the constructor:
VlcControl vlcControl1 = new VlcControl();
3) In my FormsApp_Load() added the following lines:
vlcControl1.BeginInit();
vlcControl1.VlcLibDirectory = new DirectoryInfo(_exeFolder + #"\libvlc\win-x86"); //Make sure your dir is correct
vlcControl1.VlcMediaplayerOptions = new[] { "-vv"}; //not sure what this does
vlcControl1.EndInit();
YourControlContainer.Controls.Add(vlcControl1); //Add the control to your container
vlcControl1.Dock = DockStyle.Fill; //Optional
this.vlcControl1.Click += new EventHandler(vlcControl1_Click); //Optional - added a click event .Play()
Hope this helps someone.
I've also experienced this problem.
I just look into the properties of the VlcControl on the Form and change the VlcLibDirectory item under the Media Player category by browsing to the directory which the "libvlc.dll" located.
(in my application C:\Users\MCOT\source\repos\WindowsApp3\packages\VideoLAN.LibVLC.Windows.3.0.6\build\x86)
#Thanin's answer is what I needed, ... here is a code snippet to where the library should be installed.
//InitializeComponent();
using (RegistryKey rk = Registry.LocalMachine.OpenSubKey(
"SOFTWARE\\VideoLAN\\VLC",
RegistryKeyPermissionCheck.ReadSubTree,
RegistryRights.QueryValues))
{
_Vlc.SourceProvider.CreatePlayer(
new DirectoryInfo(rk.GetValue("InstallDir") as string),
new string[] { });
}
I am working from the sample project here: http://www.codeproject.com/Articles/8086/Extending-the-save-file-dialog-class-in-NET
I have hidden the address/location bar at the top and made other modifications but I can't for the life of me manage to disable the button that lets you go up to the parent folder. Ist is in the ToolbarWindow32 class which is the problem. This is what I have at the moment but it is not working:
int parentFolderWindow = GetDlgItem(parent, 0x440);
//Doesn't work
//ShowWindow((IntPtr)parentFolderWindow, SW_HIDE);
//40961 gathered from Spy++ watching messages when clicking on the control
// doesn't work
//SendMessage(parentFolderWindow, TB_ENABLEBUTTON, 40961, 0);
// doesn't work
//SendMessage(parentFolderWindow, TB_SETSTATE, 40961, 0);
//Comes back as '{static}', am I working with the wrong control maybe?
GetClassName((IntPtr)parentFolderWindow, lpClassName, (int)nLength);
Alternatively, if they do use the parent folder button and go where I don't want them to, I'm able to look at the new directory they land in, is there a way I can force the navigation to go back?
Edit: Added screenshot
//Comes back as '{static}', am I working with the wrong control maybe?
You know you are using the wrong control, you expected to see "ToolbarWindow32" back. A very significant problem, a common one for Codeproject.com code, is that this code cannot work anymore as posted. Windows has changed too much since 2004. Vista was the first version since then that added a completely new set of shell dialogs, they are based on IFileDialog. Much improved over its predecessor, in particular customizing the dialog is a lot cleaner through the IFileDialogCustomize interface. Not actually what you want to do, and customizations do not include tinkering with the navigation bar.
The IFileDialogEvents interface delivers events, the one you are looking for is the OnFolderChanging event. Designed to stop the user from navigating away from the current folder, the thing you really want to do.
While this looks good on paper, I should caution you about actually trying to use these interfaces. A common problem with anything related to the Windows shell is that they only made it easy to use from C++. The COM interfaces are the "unfriendly" kind, interfaces based on IUnknown without a type library you can use the easily add a reference to your C# or VB.NET project. Microsoft published the "Vista bridge" to make these interfaces usable from C# as well, it looks like this. Yes, yuck. Double yuck when you discover you have to do this twice, this only works on later Windows versions and there's a strong hint that you are trying to do this on XP (judging from the control ID you found).
This is simply not something you want to have to support. Since the alternative is so simple, use the supported .NET FileOk event instead. A Winforms example:
private void SaveButton_Click(object sender, EventArgs e) {
string requiredDir = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
using (var dlg = new SaveFileDialog()) {
dlg.InitialDirectory = requiredDir;
dlg.FileOk += (s, cea) => {
string selectedDir = System.IO.Path.GetDirectoryName(dlg.FileName);
if (string.Compare(requiredDir, selectedDir, StringComparison.OrdinalIgnoreCase) != 0) {
string msg = string.Format("Sorry, you cannot save to this directory.\r\nPlease select '{0}' instead", requiredDir);
MessageBox.Show(msg, "Invalid folder selection");
cea.Cancel = true;
}
};
if (dlg.ShowDialog() == DialogResult.OK) {
// etc...
}
}
}
I don't this is going to work. Even if you disable the button they can type ..\ and click save and it will take them up one level. You can't exactly disable the file name text box and maintain the functionality of the dialog.
You'd be better off either using the FolderBrowserDialog and setting it's RootFolder property and asking the user to type the filename in or auto generating it.
If the folder you are wanting to restrict the users to isn't an Environment.SpecialFolder Then you'll need to do some work to make the call to SHBrowseForFolder Manually using ILCreateFromPath to get a PIDLIST_ABSOLUTE for your path to pass to the BROWSEINFO.pidlRoot
You can reflect FolderBrowserDialog.RunDialog to see how to make that call.
Since you want such custom behaviors instead of developing low level code (that is likely yo break in the next versions of windows) you can try to develop your file picker form.
Basically it is a simple treeview + list view. Microsoft has a walk-through .
It will take you half a day but once you have your custom form you can define all behaviors you need without tricks and limits.