WinForms. How to find TableAdapter and Fill DataTable at Design-Time - winforms

Data in standart DataGridView looks too empty
and it is unclear how data look in customized, filled grid
I'm going to extend grid to look more complex but the result it will be visible only at run-time, when it have data.
So I want to fill DataTable at design time to see how the result looks in the Grid.
So I need to perfrom code
this.MyDataTableTableAdapter.Fill(this.dBTestDataSet.MyDataTable)
I can get this.dBTestDataSet.MyDataTable from
DataSource when DataSource is assigned by BindingSource using next code:
if ((DataSource is BindingSource) && String.IsNullOrEmpty(DataMember))
{
BindingSource bindingSource = (DataSource as BindingSource);
if ((bindingSource.DataSource is DataSet) && (bindingSource.DataMember is String))
{
DataSet dataSet = (bindingSource.DataSource as DataSet);
DataTable dataTable = dataSet.Tables[bindingSource.DataMember];
return dataTable;
}
}
now I have MyDataTable
but how can I get MyDataTableTableAdapter?
I don't see any connection between MyDataTable and MyDataTableTableAdapter in the porject code
Although the development environment correctly shows the DataAdapter under every DataTable.

I believe it's not a good idea to load actual data to designer. You can fill DataGridView using some fake data, the same way which web forms designer do it for ASP.NET GridView control.
Q: How can I get MyDataTableTableAdapter? I don't see any connection between MyDataTable and
MyDataTableTableAdapter in the porject code.
Each dataset has an XSD file which contains the definition of data set schema and also contains some annotations which will be used for generating TableAdapter classes, for example it contains some nodes like:
<TableAdapter BaseClass="System.ComponentModel.Component"
DataAccessorModifier="AutoLayout, AnsiClass, Class, Public"
DataAccessorName="CategoryTableAdapter"
GeneratorDataComponentClassName="CategoryTableAdapter"
Name="Category"
UserDataComponentName="CategoryTableAdapter">
You can parse the file and extract the related TableAdapter for the DataTable. As you can see above xml is the point which TableAdapter and DataTable relates to each other.
You can get the xsd file using Visual Studio API methods. Also as a more simple workaround you can set its BuildAction to Embedded Resource and get it from the compiled assembly.
Note
Another option which may satisfy your requirement is putting your DataGridView control in UserControl and load data in constructor of UserControl, this way when you put an instance of your UserControl on a form, since the designer executes the constructor of your UserControl then the DataGridView will be filled with data.
Then if you want to expose Columns property of DataGridView you can use this solution.
Example - Just for learning purpose
using System;
using System.Linq;
using System.Windows.Forms;
using System.Data;
using System.Reflection;
using System.IO;
using System.Xml.Linq;
public class MyDataGridView : DataGridView
{
protected override void OnDataSourceChanged(EventArgs e)
{
base.OnDataSourceChanged(e);
TryToPopulateControl();
}
protected override void OnDataMemberChanged(EventArgs e)
{
base.OnDataMemberChanged(e);
TryToPopulateControl();
}
private void TryToPopulateControl()
{
if (!DesignMode)
return;
else
{
try
{
if (this.DataSource is BindingSource)
{
var bs = (BindingSource)this.DataSource;
if (bs.DataSource is DataSet)
{
var ds = bs.DataSource;
var table = bs.DataMember;
if (ds == null || string.IsNullOrEmpty(table))
return;
var name = ds.GetType().FullName + ".xsd";
string result = "";
using (var stream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream(name))
using (StreamReader reader = new StreamReader(stream))
result = reader.ReadToEnd();
var document = XDocument.Parse(result);
var node = document.Descendants()
.Where(x => x.Name.LocalName == "TableAdapter")
.Where(x => x.Attribute("Name").Value == table).FirstOrDefault();
if (node != null)
{
var tableAdapterName = node.Attribute("UserDataComponentName").Value;
var adapterType = Assembly.GetExecutingAssembly()
.GetTypes().Where(x => x.Name == tableAdapterName).FirstOrDefault();
var adapter = Activator.CreateInstance(adapterType);
var fillMethod = adapterType.GetMethod("GetData");
var dataTable = fillMethod.Invoke(adapter, new object[] { }) as DataTable;
foreach (DataRow row in dataTable.Rows)
((DataSet)ds).Tables[table].Rows.Add(row.ItemArray);
}
}
}
}
catch (Exception)
{
//Could not load data
}
}
}
}

Related

Moving from SQL Databinding to using the Entity framework

I'm changing my WPF application to use the Entity Framework instead of calling sql db directly.
On this one window I have a listview containing a gridview and I'm databinding it by using the following method which calls a stored procedure to get the data.
Now I already have my model generated from my existing sql database and included the stored proc...
How would I go about changing this method to read the data from the entity model instead of directly from sql?
public static void BindData(DataGrid grid)
{
SqlConnection loginCon = new SqlConnection();
loginCon.ConnectionString = ConfigurationManager.ConnectionStrings["ConString"].ConnectionString;
using (SqlDataAdapter adapter = new SqlDataAdapter("sp_SELECT_CONSHEAD", loginCon))
{
DataSet data = new DataSet();
adapter.Fill(data);
grid.ItemsSource = data.Tables[0].DefaultView;
}
}
You can use Function Import to get the stored procedure mapped to entity in EntityFramework. Then you can directly call the function in your code with single line of code.
grid.ItemsSource = dbContext.GetSP_Select_Conshead();

multiple views of an observablecollection filtered by element data

I am building a WPF application that takes in rows of data, and outputs them into different tabs in the GUI based on the data contained in the row. However, the tabs aren't known until runtime, so I need to dynamically build an unknown number of tabs with collection views with different filters off of my main ObservableCollection.
The problem I have been running in to is that using ListCollectionViews I need a predicate filter but I do not know of a way to have a dynamic predicate based on a local variable? I tried variable capturing, but that just changes all of my filters each time a new tab is added.
//class variables
string currTab;
public ObservableCollection<MyData> myCollection = new ObservableCollection<myData>();
private void DataAdd(object sender, RoutedEventArgs e)
{
currTab = inputData.ToString();
ListCollectionView c = new ListCollectionView(myCollection);
c.Filter = new Predicate<object>(MyFilter);
}
public bool MyFilter(object foo)
{
if (foo).ToString() != currTab)
return false;
else
return true;
}
I also tried using lambda expressions and with ICollectionView, but the collection doesn't update with new values, so I just see empty tabs.
CollectionView c = new CollectionViewSource { Source = myCollection.Where(z => z.ToString() == tabName) }.View;
Is there a way to make either of these approaches work? Or a better way to do this?
it turns out I just needed to use a local variable for the predicate
var b = currTab
c.Filter = (foo) =>{return foo.ToString() == b;};

Silverlight, connecting service.cs and XAML.cs for database

I've found a sample showing how to connect to SQL server (a local MDF), but it uses databinding, how can I use SQL in the normal way (insert, select, update, delete..., sqldatareader, without binding), I think all SQL staff should be performed in service.cs, so how can I use SQL from my XAML.cs? here is the service code:
and here service.cs:
public class ServiceCustomer : IServiceCustomer
{
public clsCustomer getCustomer(int intCustomer)
{
SqlConnection objConnection = new SqlConnection();
DataSet ObjDataset = new DataSet();
SqlDataAdapter objAdapater = new SqlDataAdapter();
SqlCommand objCommand = new SqlCommand("Select * from Customer where CustomerId=" + intCustomer.ToString());
objConnection.ConnectionString = System.Configuration.ConfigurationManager.ConnectionStrings["ConnStr"].ToString();
objConnection.Open();
objCommand.Connection = objConnection;
objAdapater.SelectCommand = objCommand;
objAdapater.Fill(ObjDataset);
clsCustomer objCustomer = new clsCustomer();
objCustomer.CustomerCode = ObjDataset.Tables[0].Rows[0][0].ToString();
objCustomer.Customer = ObjDataset.Tables[0].Rows[0][1].ToString();
objConnection.Close();
return objCustomer;
}
}
and my page.xaml.cs:
public Page()
{
InitializeComponent();
ServiceCustomerClient obj = new ServiceCustomerClient();
obj.getCustomerCompleted += new EventHandler<getCustomerCompletedEventArgs>(DisplayResults);
obj.getCustomerAsync(1);
}
void DisplayResults(object sender, getCustomerCompletedEventArgs e)
{
LayoutRoot.DataContext = e.Result;
}
how can I insert a value into my dayabase right from my page.xaml.cs? how can I make a connection between service and xaml objects and functions?
thanks
The Page.xaml.cs file is not autogenerated in a standard silverlight project (beyond it's initial creation via template), so any changes you make to the class will persist.
If your current project does some autogen of xaml classes then you can use the partial class feature of C# Partial Classes in C# Guide to add to the class without worrying that the autogen will remove your additions.
You would need to add another method within your service class that performs the insert, and then pass it the values from your form.
public class ServiceCustomer : IServiceCustomer
{
public clsCustomer getCustomer(int intCustomer)
{
...
}
public void addCustomer(clsCustomer newCustomer)
{
//Code to add the customer to the database
}
}
to call this from your code you could use the following code, all calls to the service in silverlight are Asynchronous calls therefore they are called in a seperate thread but since the following call has no return there is no need for a completed event handler.
public void foo
{
clsCustomer cust = new clsCustomer();
//Create your customer object here
ServiceCustomerClient obj = new ServiceCustomerClient();
obj.AddCustomerAsync(cust);
}
this function could then be called when ever you want to add a customer, E.g. when a button is clicked
public void somebutton_Click(object sender, RoutedEventArgs e)
{
foo();
}
If you need any more information let me know.

linq reflection WInForms

I am very new to linq and am trying to figure out how to accomplish the following:
Currently, I have a Winforms project that has a Base Form with a DataRow as one of it's members. I have several derived Forms populate the DataRow based on data from a DataTable (SQL Query Result). There are controls on the derived Forms that are populated with the values from the data as well. When the Save button on the derived Forms is clicked, the DataRow in the Base Form is updated and then the Derived Form updates the Database via a DataAdapter.
I wanted to replace all of the SQL Commands using linqs so I tried implementing this functionality using LINQ by the following:
I created my Linq query in the Derived Form and assigned the result to an Object in the Base Form. I cast the Object in the Base Form to the class type of the Linq query and use reflection to populate all the controls on the Derived Form. When the save button is clicked I update the Object but I am not able to update the Database.
The problem that I can't solve is how to update the database once the object is updated. At this point I don't have the Data Context that I used for the linq query.
I am using an SQL function within the linq query so I had to create a separate class for these values as I was getting an anonymous type error. I am probably missing something here.
Any help would be most appreciated as I really how clean the linq code is.
Edit (Copied from Brad's Edit to Tomas's answer):
Here are the 3 steps of my code.
Step 1 - Get a singe record of data from database
private void GetDatabaseDetailData()
{
_db = new PriorityDataContext();
DetailData = (from db in _db.tblDatabases
where db.DatabaseID == Id
select db).SingleOrDefault();
DeveloperData = (from db in _db.tblDatabases
where db.DatabaseID == Id
select new DeveloperInfo
{
DeveloperName = _db.func_get_employee_name(db.Developer)
}).SingleOrDefault();
}
Step 2 - Populate all controls whos name exists in the Object. The DetailData Object is cast to the specific type passed into this method. All code not shown for brevity.
protected virtual void PopulateDetailControlsA(List<Control> controlContainers, string srcDataTableName)
{
Object data = null;
Type type = null;
switch (srcDataTableName)
{
case "tblDatabases" :
type = typeof(tblDatabase);
data = (tblDatabase)DetailData;
break;
}
if (type != null)
{
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var controlContainer in controlContainers)
{
foreach (var propertyInfo in properties)
{
if (!ControlExists(controlContainer, propertyInfo.Name)) continue;
var txtControl = controlContainer.Controls[propertyInfo.Name] as ExtendedTextBox;
if (txtControl != null)
{
try
{
var value = propertyInfo.GetValue(data, null).ToString();
if (propertyInfo.Name == "row_oper_name" || propertyInfo.Name == "row_last_chng_oper_name")
{
txtControl.Text = RowOperatorData.RowOperatorName;
txtControl.ValueMember = propertyInfo.GetValue(data, null).ToString();
}
else
txtControl.Text = value;
}
catch (NullReferenceException)
{
}
continue;...........
Step 3 - Try and save changes back to database in the derived From.
private void SaveData()
{
try
{
_db.SubmitChanges();
}
catch (Exception sqlException)
{
}
}
What I am really unclear about hear is how to store the result set in the Base Form so that I can use the same code for many different queries. The DataRow worked great because I use the some code for over 25 derive Forms.
If I understand you correctly, you create the DataContext in a derived form and then use it to write some queries (in a derived form). In order to be able to update the database, your queries must return the entities obtained from the table (i.e. the select clause should just return the entity). For example:
DataContext db = // ...
var q = from p in db.Things
where p.Some > 10 select p;
If you then modify the entities, you can use db.SubmitChanges() to store the changes (made to the entity objects) to the database. For this, you need the original db value.
In your scenario, you'll need to store the DataContext (as a field) in the derived form. If you need to perform the update from the base form, then I suggest you define a virtual method:
// Base form
protected abstract void UpdateDatabase();
// Derived from with field 'db' storing 'DataContext'
protected override void UpdateDatabase() {
db.SumbitChanges();
}

Open directory dialog

I want the user to select a directory where a file that I will then generate will be saved. I know that in WPF I should use the OpenFileDialog from Win32, but unfortunately the dialog requires file(s) to be selected - it stays open if I simply click OK without choosing one. I could "hack up" the functionality by letting the user pick a file and then strip the path to figure out which directory it belongs to but that's unintuitive at best. Has anyone seen this done before?
You can use the built-in FolderBrowserDialog class for this. Don't mind that it's in the System.Windows.Forms namespace.
using (var dialog = new System.Windows.Forms.FolderBrowserDialog())
{
System.Windows.Forms.DialogResult result = dialog.ShowDialog();
}
If you want the window to be modal over some WPF window, see the question How to use a FolderBrowserDialog from a WPF application.
EDIT: If you want something a bit more fancy than the plain, ugly Windows Forms FolderBrowserDialog, there are some alternatives that allow you to use the Vista dialog instead:
Third-party libraries, such as Ookii dialogs (.NET 4.5+)
The Windows API Code Pack-Shell:
using Microsoft.WindowsAPICodePack.Dialogs;
...
var dialog = new CommonOpenFileDialog();
dialog.IsFolderPicker = true;
CommonFileDialogResult result = dialog.ShowDialog();
Note that this dialog is not available on operating systems older than Windows Vista, so be sure to check CommonFileDialog.IsPlatformSupported first.
I created a UserControl which is used like this:
<UtilitiesWPF:FolderEntry Text="{Binding Path=LogFolder}" Description="Folder for log files"/>
The xaml source looks like this:
<UserControl x:Class="Utilities.WPF.FolderEntry"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel>
<Button Margin="0" Padding="0" DockPanel.Dock="Right" Width="Auto" Click="BrowseFolder">...</Button>
<TextBox Height="Auto" HorizontalAlignment="Stretch" DockPanel.Dock="Right"
Text="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
</DockPanel>
</UserControl>
and the code-behind
public partial class FolderEntry {
public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(FolderEntry), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public static DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(string), typeof(FolderEntry), new PropertyMetadata(null));
public string Text { get { return GetValue(TextProperty) as string; } set { SetValue(TextProperty, value); }}
public string Description { get { return GetValue(DescriptionProperty) as string; } set { SetValue(DescriptionProperty, value); } }
public FolderEntry() { InitializeComponent(); }
private void BrowseFolder(object sender, RoutedEventArgs e) {
using (FolderBrowserDialog dlg = new FolderBrowserDialog()) {
dlg.Description = Description;
dlg.SelectedPath = Text;
dlg.ShowNewFolderButton = true;
DialogResult result = dlg.ShowDialog();
if (result == System.Windows.Forms.DialogResult.OK) {
Text = dlg.SelectedPath;
BindingExpression be = GetBindingExpression(TextProperty);
if (be != null)
be.UpdateSource();
}
}
}
}
As stated in earlier answers, FolderBrowserDialog is the class to use for this. Some people have (justifiable) concerns with the appearance and behaviour of this dialog. The good news is that it 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 (you're out of luck if still using NET Framework though).
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.
Ookii folder dialog can be found at Nuget.
PM> Install-Package Ookii.Dialogs.Wpf
And, example code is as below.
var dialog = new Ookii.Dialogs.Wpf.VistaFolderBrowserDialog();
if (dialog.ShowDialog(this).GetValueOrDefault())
{
textBoxFolderPath.Text = dialog.SelectedPath;
}
More information on how to use it: https://github.com/augustoproiete/ookii-dialogs-wpf
For those who don't want to create a custom dialog but still prefer a 100% WPF way and don't want to use separate DDLs, additional dependencies or outdated APIs, I came up with a very simple hack using the Save As dialog.
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.
Ookii Dialogs includes a dialog for selecting a folder (instead of a file):
https://github.com/ookii-dialogs
For Directory Dialog to get the Directory Path, First Add reference System.Windows.Forms, and then Resolve, and then put this code in a button click.
var dialog = new FolderBrowserDialog();
dialog.ShowDialog();
folderpathTB.Text = dialog.SelectedPath;
(folderpathTB is name of TextBox where I wana put the folder path, OR u can assign it to a string variable too i.e.)
string folder = dialog.SelectedPath;
And if you wana get FileName/path, Simply do this on Button Click
FileDialog fileDialog = new OpenFileDialog();
fileDialog.ShowDialog();
folderpathTB.Text = fileDialog.FileName;
(folderpathTB is name of TextBox where I wana put the file path, OR u can assign it to a string variable too)
Note: For Folder Dialog, the System.Windows.Forms.dll must be added to the project, otherwise it wouldn't work.
I found the below code on below link... and it worked
Select folder dialog WPF
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'd suggest, to add in the nugget package:
Install-Package OpenDialog
Then the way to used it is:
Gat.Controls.OpenDialogView openDialog = new Gat.Controls.OpenDialogView();
Gat.Controls.OpenDialogViewModel vm = (Gat.Controls.OpenDialogViewModel)openDialog.DataContext;
vm.IsDirectoryChooser = true;
vm.Show();
WPFLabel.Text = vm.SelectedFilePath.ToString();
Here's the documentation:
http://opendialog.codeplex.com/documentation
Works for Files, files with filter, folders, etc
The best way to achieve what you want is to create your own wpf based control , or use a one that was made by other people
why ? because there will be a noticeable performance impact when using the winforms dialog in a wpf application (for some reason)
i recommend this project
https://opendialog.codeplex.com/
or Nuget :
PM> Install-Package OpenDialog
it's very MVVM friendly and it isn't wraping the winforms dialog
The Ookii VistaFolderBrowserDialog is the one you want.
If you only want the Folder Browser from Ooki Dialogs and nothing else then download the Source, cherry-pick the files you need for the Folder browser (hint: 7 files) and it builds fine in .NET 4.5.2. I had to add a reference to System.Drawing. Compare the references in the original project to yours.
How do you figure out which files? Open your app and Ookii in different Visual Studio instances. Add VistaFolderBrowserDialog.cs to your app and keep adding files until the build errors go away. You find the dependencies in the Ookii project - Control-Click the one you want to follow back to its source (pun intended).
Here are the files you need if you're too lazy to do that ...
NativeMethods.cs
SafeHandles.cs
VistaFolderBrowserDialog.cs
\ Interop
COMGuids.cs
ErrorHelper.cs
ShellComInterfaces.cs
ShellWrapperDefinitions.cs
Edit line 197 in VistaFolderBrowserDialog.cs unless you want to include their Resources.Resx
throw new InvalidOperationException(Properties.Resources.FolderBrowserDialogNoRootFolder);
throw new InvalidOperationException("Unable to retrieve the root folder.");
Add their copyright notice to your app as per their license.txt
The code in \Ookii.Dialogs.Wpf.Sample\MainWindow.xaml.cs line 160-169 is an example you can use but you will need to remove this, from MessageBox.Show(this, for WPF.
Works on My Machine [TM]
None of these answers worked for me (generally there was a missing reference or something along those lines)
But this quite simply did:
Using FolderBrowserDialog in WPF application
Add a reference to System.Windows.Forms and use this code:
var dialog = new System.Windows.Forms.FolderBrowserDialog();
System.Windows.Forms.DialogResult result = dialog.ShowDialog();
No need to track down missing packages. Or add enormous classes
This gives me a modern folder selector that also allows you to create a new folder
I'm yet to see the impact when deployed to other machines
I know this is an old question, but a simple way to do this is use the FileDialog option provided by WPF and using System.IO.Path.GetDirectory(filename).
You could use smth like this in WPF. I've created example method.
Check below.
public string getFolderPath()
{
// Create OpenFileDialog
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Multiselect = false;
openFileDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
if (openFileDialog.ShowDialog() == true)
{
System.IO.FileInfo fInfo = new System.IO.FileInfo(openFileDialog.FileName);
return fInfo.DirectoryName;
}
return null;
}
It seems that the Microsoft.Win32 .NET library does not support selecting folders (only files), so you are out of luck in WPF (as of 7/2022). I feel the best option now is Ookii for WPF: https://github.com/ookii-dialogs/ookii-dialogs-wpf. It works great and as expected in WPF minus Microsoft support. You can get it as a NuGet package. Code behind XAML View:
public partial class ExportRegionView : UserControl
{
public ExportRegionView()
{
InitializeComponent();
}
private void SavePath(object sender, RoutedEventArgs e)
{
var dialog = new Ookii.Dialogs.Wpf.VistaFolderBrowserDialog();
dialog.Description = "SIPAS Export Folder";
dialog.UseDescriptionForTitle = true;
if (dialog.ShowDialog().GetValueOrDefault())
{
ExportPath.Text = dialog.SelectedPath;
}
}
}
XAML: <Button Grid.Row="1" Grid.Column="3" Style="{DynamicResource Esri_Button}" Click="SavePath" Margin="5,5,5,5">Path</Button>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Gearplay
{
/// <summary>
/// Логика взаимодействия для OpenFolderBrows.xaml
/// </summary>
public partial class OpenFolderBrows : Page
{
internal string SelectedFolderPath { get; set; }
public OpenFolderBrows()
{
InitializeComponent();
Selectedpath();
InputLogicalPathCollection();
}
internal void Selectedpath()
{
Browser.Navigate(#"C:\");
Browser.Navigated += Browser_Navigated;
}
private void Browser_Navigated(object sender, NavigationEventArgs e)
{
SelectedFolderPath = e.Uri.AbsolutePath.ToString();
//MessageBox.Show(SelectedFolderPath);
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
}
string [] testing { get; set; }
private void InputLogicalPathCollection()
{ // add Menu items for Cotrol
string[] DirectoryCollection_Path = Environment.GetLogicalDrives(); // Get Local Drives
testing = new string[DirectoryCollection_Path.Length];
//MessageBox.Show(DirectoryCollection_Path[0].ToString());
MenuItem[] menuItems = new MenuItem[DirectoryCollection_Path.Length]; // Create Empty Collection
for(int i=0;i<menuItems.Length;i++)
{
// Create collection depend how much logical drives
menuItems[i] = new MenuItem();
menuItems[i].Header = DirectoryCollection_Path[i];
menuItems[i].Name = DirectoryCollection_Path[i].Substring(0,DirectoryCollection_Path.Length-1);
DirectoryCollection.Items.Add(menuItems[i]);
menuItems[i].Click += OpenFolderBrows_Click;
testing[i]= DirectoryCollection_Path[i].Substring(0, DirectoryCollection_Path.Length - 1);
}
}
private void OpenFolderBrows_Click(object sender, RoutedEventArgs e)
{
foreach (string str in testing)
{
if (e.OriginalSource.ToString().Contains("Header:"+str)) // Navigate to Local drive
{
Browser.Navigate(str + #":\");
}
}
}
private void Goback_Click(object sender, RoutedEventArgs e)
{// Go Back
try
{
Browser.GoBack();
}catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void Goforward_Click(object sender, RoutedEventArgs e)
{ //Go Forward
try
{
Browser.GoForward();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void FolderForSave_Click(object sender, RoutedEventArgs e)
{
// Separate Click For Go Back same As Close App With send string var to Main Window ( Main class etc.)
this.NavigationService.GoBack();
}
}
}

Resources