I am trying to change the language my WPF app uses in a click event but it doesn't change.
private void menuItemGerman_Click(object sender, RoutedEventArgs e)
{
Settings.Default.Culture = "de-DE";
Thread.CurrentThread.CurrentCulture = new CultureInfo(Settings.Default.Culture);
Thread.CurrentThread.CurrentUICulture = new CultureInfo(Settings.Default.Culture);
}
What am I missing?
What am I missing?
You changed the culture registered with the thread, and String.Format will use this now, but you need to reload all localized items in the WPF hierarchy.
WPF Localization – On-the-fly Language Selection has more information.
If you have resource files, e.g.:
Resources.resx
Resources.hu-hu.resx
... and want to change the localization at runtime,
... and do not want to mess with additional resource dictionaries and recoding all UI localizations,
it will work with the
Thread.CurrentThread.CurrentUICulture = new CultureInfo(lang);
But it will not change the already shown window's language.
To achieve that, more coding is required - the Application lifecycle must be managed, instead of the default.
First, remove the StartupUri from App.xaml:
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ADUI.App"
xmlns:System="clr-namespace:System;assembly=mscorlib" >
<!--StartupUri="wndMain.xaml">-->
<Application.Resources>
</Application.Resources>
Second, implement a class, which is now responsible for the application lifecycle:
public class LocApp: Application
{
[STAThread]
public static void Main()
{
App app = new App();
app.ShutdownMode = ShutdownMode.OnExplicitShutdown;
wndMain wnd = new wndMain();
wnd.Closed += Wnd_Closed;
app.Run(wnd);
}
private static void Wnd_Closed(object sender, EventArgs e)
{
wndMain wnd = sender as wndMain;
if (!string.IsNullOrEmpty(wnd.LangSwitch))
{
string lang = wnd.LangSwitch;
wnd.Closed -= Wnd_Closed;
Thread.CurrentThread.CurrentUICulture = new CultureInfo(lang);
wnd = new wndMain();
wnd.Closed += Wnd_Closed;
wnd.Show();
}
else
{
App.Current.Shutdown();
}
}
}
Do not forget to change the startup object on your Project properties / Application page to LocApp!
Finally, implement some code which switches the languages in the main window's code:
public partial class wndMain : Window
{
public string LangSwitch { get; private set; } = null;
// ... blah, blah, blah
private void tbEn_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
LangSwitch = "en";
Close();
}
private void tbHu_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
LangSwitch = "hu-hu";
Close();
}
// ... blah, blah, blah
}
Make sure, that the provided localization code matches with one of the resx file language code ("hu-hu" in this example)!
This solution will close and reopen the main window, with the chosen language, and will exit if the main window closed by other means.
I also experienced this problem and my solution was:
Resources.en-US
Resources.pt-PT
I created a class that will return a dictionary with the key and the label:
public class Labels : ObservableObject
{
public Dictionary<string, string> Items { get; set; }
public string this[string name]
{
get
{
return Items.ContainsKey(name) ? Items[name] : "";
}
}
public Labels()
{
Items = new Dictionary<string, string>();
}
}
Next, one more class to get the Resources:
public static class LanguageUtils
{
public static Labels GetLangLables(string label)
{
var resources = Resources.ResourceManager.GetResourceSet(new CultureInfo(label), true, true);
return new Labels
{
Items = resources.Cast<DictionaryEntry>().ToDictionary(r => r.Key.ToString(), r => r.Value.ToString())
};
}
}
When you need some language:
LanguageUtils.GetLangLables("pt-PT");
Once, you can't raise (RaisePropertyChanged()) static properties, use this:
public class LanguageContext
{
private static LanguageContext _languageContext;
public static LanguageContext Instance
{
get
{
if (_languageContext == null)
{
_languageContext = new LanguageContext();
}
return _languageContext;
}
}
protected LanguageContext()
{
CurrentLangLabels = LanguageUtils.GetLangLables("en-US");
}
public Labels CurrentLangLabels { get; set; }
}
Now you can update language:
LanguageContext.Instance.CurrentLangLabels = LanguageUtils.GetLangLables(SelectedLanguage.Resource);
Raise like this:
public Labels CurrentLangLabels
{
get { return LanguageContext.Instance.CurrentLangLabels; }
set { RaisePropertyChanged(); }
}
And use label:
CurrentLangLabels.Items[LabelName]
this might come in handy for someone. I have used the advice given by George & Chris Schaller above to make this work in my project without creating new Application class.
public string LangSwitch { get; private set; } = null;
private void BtnLngPl_Click(object sender, RoutedEventArgs e)
{
CultureInfo current = CultureInfo.CurrentUICulture;
CultureInfo newUiCulture;
if (current.Name.Equals("en-US"))
{
newUiCulture = new CultureInfo("pl");
Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
if (System.Windows.Application.Current.MainWindow != null)
((MainWindow)System.Windows.Application.Current.MainWindow).Closed += Wnd_Closed;
Thread.CurrentThread.CurrentCulture = newUiCulture;
Thread.CurrentThread.CurrentUICulture = newUiCulture;
LangSwitch = "pl";
Close();
}
else
newUiCulture = new CultureInfo("en-US");
CultureInfo.CurrentUICulture = newUiCulture;
Console.WriteLine(#"The current UI culture is now {0}",
CultureInfo.CurrentUICulture.Name);
}
and the other necessary code can be taken from George's answer. Not sure how good of a solution this is, but does the job for me.
Related
I have these objects in my project:
SchedulerList
SchedulerListItem
SchedulerListItemDetails
each one is a win forms control, which are used in forms of my application. The SchedulerList holds SchedulerListItems and each item can have SchedulerListItemDetails.
my code goes as follows:
//creating my initial list form
FrmListTesting f = new FrmListTesting();
f.Show();
The form has only one button that has a hard-coded parameter for testing purposes, as well as a SchedulerList control taht will hold the list items.
When the button is clicked the form does the following:
private void button1_Click(object sender, EventArgs e)
{
var control = this.Controls[1] as SchedulerList;
var path = #"D:\Share\Countries.txt";
var sli = new SchedulerListItem(path);
control.AddItem(sli);
}
my SchedulerListItem constuctor goes as follows:
public SchedulerListItem(string path)
{
InitializeComponent();
this.Name = Path.GetFileNameWithoutExtension(path);
this.SourcePath = path;
this.DestinationPath = GetDestinationPath(path);
}
And the AddItem method is defined as:
public void AddItem(SchedulerListItem item)
{
this.flPanel.Controls.Add(item);
}
The add item method works as intended, displays all the data that was required and displays it in the UI. The list item has a button that brings up the details form as such:
//the form constructor
public FrmSchedulerItemDetails(SchedulerListItem item)
{
InitializeComponent();
this.detailsControl = new SchedulerListItemDetails(item, this);
}
//control constructor
public SchedulerListItemDetails(SchedulerListItem item, Form owner)
{
InitializeComponent();
this.SourcePath = item.SourcePath;
this.DestinationPath = item.DestinationPath;
this.OldFormat = item.OldFormat;
this.ExportToExcel = item.ExportToExcel;
this.owner = owner;
this.underlyingItem = item;
}
And now the problem. After the SchedulerListItemDetails constructor is called and the data "gets initialized", when i look at the data inside the object its set to default values. it seams that everything that I set after InitializeComponent(); gets ignored.
things that i have tried:
hard-coding the values to see if primitives get passed correctly
settings breakpoints on every InitializeComponent() method to see the stack trace associated with setting to default values
none of the methods show any results... I know that if i use a form directly instead of using a control within a from i can set the values the way i want to, but I'm very confused as to why this other method with controls doesn't work.
EDIT 1:
the code for SchedulerListItemDetails:
public partial class SchedulerListItemDetails : UserControl
{
public SchedulerListItemDetails(SchedulerListItem item, Form owner)
{
InitializeComponent();
this.SourcePath = item.SourcePath;
this.DestinationPath = item.DestinationPath;
this.OldFormat = item.OldFormat;
this.ExportToExcel = item.ExportToExcel;
this.owner = owner;
this.underlyingItem = item;
}
public SchedulerListItemDetails()
{
InitializeComponent();
}
private Form owner = null;
private SchedulerListItem underlyingItem;
public Boolean ExportToExcel
{
get
{
return this.cbxExcel.Checked;
}
set
{
this.cbxExcel.Checked = value;
}
}
public Boolean OldFormat
{
get
{
return this.cbxOldFormat.Checked;
}
set
{
this.cbxOldFormat.Checked = value;
}
}
public String DestinationPath
{
get
{
return this.tbxDestinationPath.Text;
}
set
{
this.tbxDestinationPath.Text = value;
}
}
public String SourcePath
{
get
{
return this.tbxSourcePath.Text;
}
set
{
this.tbxSourcePath.Text = value;
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
this.owner.Close();
}
private void btnSave_Click(object sender, EventArgs e)
{
underlyingItem.SourcePath = this.SourcePath;
underlyingItem.DestinationPath = this.DestinationPath;
underlyingItem.OldFormat = this.OldFormat;
underlyingItem.ExportToExcel = this.ExportToExcel;
btnCancel_Click(sender, e);
}
}
I'll make an answer, because it should help you to solve your problem.
You have default (parameterless) constructor, which may be called and if it is called, then your constructor with parameters is not called.
Proper design would be something like
public partial class SchedulerListItemDetails : UserControl
{
public SchedulerListItemDetails()
{
InitializeComponent();
}
public SchedulerListItemDetails(SchedulerListItem item, Form owner): this()
{
this.SourcePath = item.SourcePath;
...
}
}
Notice this(), this ensure what parameterless constructor is called before (and InitializeComponent() as well, no need to duplicate it in another constructor).
Back to your problem. In your case it's like this
public partial class SchedulerListItemDetails : UserControl
{
public SchedulerListItemDetails()
{
InitializeComponent();
}
public SchedulerListItemDetails(SchedulerListItem item, Form owner)
{
InitializeComponent();
this.SourcePath = item.SourcePath;
...
}
}
Only one constructor can be called. So if you put breakpoint in parameterless one and it's triggered, then you have problems. Because you create somewhere SchedulerListItemDetails without setting it's properties (they stay default).
More likely problem is that you create new instance of that object (either before or after constructing proper, if your code ever construct such object) and that instance is what you inspect later.
So after i got a quick course of how win forms work i figured out what the problem was.
my code that i thought was enough is:
public FrmSchedulerItemDetails(SchedulerListItem item)
{
InitializeComponent();
this.DetailsControl = new SchedulerListItemDetails(item, this);
}
public SchedulerListItemDetails DetailsControl
{
get
{
return this.detailsControl;
}
set
{
this.detailsControl = value;
}
}
the this.detailsControl is the control im trying to setup, but as i have learned the correct way of replacing a component for a new one is:
public FrmSchedulerItemDetails(SchedulerListItem item)
{
InitializeComponent();
this.DetailsControl = new SchedulerListItemDetails(item, this);
}
public SchedulerListItemDetails DetailsControl
{
get
{
return this.detailsControl;
}
set
{
this.Controls.Remove(this.detailsControl);
this.detailsControl = value;
this.Controls.Add(this.detailsControl);
}
}
Feel kinda silly now :).
I need help passing data from one WPF form to another. I have a main window with two other windows that will prompt the user for information. I want to end up with all the information in the first form so that I can store the data later on. The second form must return the Reservation and Room information when you click the OK button on the second form. The third form must return the Person information when you click OK.
public partial class MainWindow : Window
{
private string message;
public MainWindow()
{
InitializeComponent();
}
protected void Exit_Click(object sender, RoutedEventArgs e)
{
Application.Current.Shutdown();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
protected void Create_Reservation_Click(object sender, RoutedEventArgs e)
{
Reservation PersonReservation = new Reservation();//Create a reservation instance
Room PersonRoom = new Room(); //Create an instance of a room
Person myPerson = new Person();//Create an instance of a person
CreateResRoom createReservationRoom = new CreateResRoom();//Create a instance of the CreateReservation WPF Form
createReservationRoom.Show();
Here it is supposed to set the room, reservation and person instance that I created equil to their corresponding instances in the CreateResRoom class.
I think the problem lies here, because it keeps continuing before it opens the CreateResRoom form.
PersonRoom = createReservationRoom.myRoom;
PersonReservation = createReservationRoom.myReservation;
}
}
That was my first class, the second and third will follow.
public partial class CreateResRoom : Window
{
Person myPerson;
public CreateResRoom()
{
InitializeComponent();
myReservation = new Reservation();
myRoom = new Room();
myPerson = new Person();
}
public Room myRoom
{
get;
set;
}
public Reservation myReservation
{
get;
set;
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
private void btnOk_Click(object sender, RoutedEventArgs e)
{
myRoom.RoomBeds = txtHeadCount.Text;
myRoom.RoomNumber = 1;
myRoom.RoomPrice = 20;
myRoom.RoomType = cboRoomType.Text;
myReservation.ResEndDate = dpEnd.ToString();
myReservation.ResStartDate = dpStart.ToString();
CreateRes createReservation = new CreateRes();
createReservation.Show();
//I think the same problem lies here that is in the MainWindow.
myPerson = createReservation.myPerson;
this.Close();
}
}
And the last class follows:
public partial class CreateRes : Window
{
public Person myPerson
{
get;
set;
}
public CreateRes()
{
InitializeComponent();
myPerson = new Person();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
private void btnOk_Click(object sender, RoutedEventArgs e)
{
myPerson.FirstName = txtFName.Text;
myPerson.LastName = txtLName.Text;
myPerson.IdNumber = Convert.ToInt32(txtIdNumber.Text);
myPerson.PhoneNumber = Convert.ToInt32(txtPhoneNumber.Text);
myPerson.AddressCity = txtAddressCity.Text;
myPerson.AddressStreet = txtAddressStreet.Text;
myPerson.AddressProvince = txtAddressProvince.Text;
myPerson.AddressPostalCode = txtAddressPostalCode.Text;
this.Close();
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
}
Just make a overload constructor which takes parameters of the window in which you want to retrieve.
Example:
Suppose we want a user to login from our MainWindow( i.e Login Window ) and we want to pass an int ID / string Email to our second form to retrieve data of logging user.
Than We have to first overload our second wpf form constructor. You can either make default constructor to do this or make an overload constructor for this work.
SecondForm:
public secondForm()
{
//Your Default Constructor Logic
}
public secondForm(string email_ )
{
//Your Overload Constructor Logic
}
Now in MainWindow from where we are logging and passing our EMail
MainWindow:
public void btnLogin()
{
//On Success
SecondWindow sw = new SecondWindow(txtBoxEMail.Content);
sw.Show();
}
A pattern you can use for this sort of thing is to have each form be responsible for creating the instance on ok click and then provide the object via a property get.
public partial class SomeForm: Window
{
public SomeClass MyProperty { get; private set; }
private void btnOk_Click(object sender, RoutedEventArgs e)
{
this.MyProperty = new SomeClass();
//additional setter logic here
this.Close();
}
}
Then you would access it from a parent form like this (notice the use of ShowDialog() http://msdn.microsoft.com/en-us/library/system.windows.window.showdialog(v=vs.110).aspx for easy checking of whether ok was clicked or not).
protected void Create_Reservation_Click(object sender, RoutedEventArgs e)
{
SomeClass myObj;
SomeOtherClass myOtherObj;
SomeForm myForm = new SomeForm();
if(myForm.Show().Value)
{
myObj = myForm.MyProperty;
}
SomeOtherForm myOtherForm = new SomeOtherForm();
if(myOtherForm.ShowDialog().Value)
{
myOtherObj = myOtherForm.MyOtherProp;
}
//save myObj & myOtherObj or whatever you need to do with them
Use the "normal way", here is a short overview.
First create a Data Context:
public class DC_Reservation() : INotifyPropertyChanged {
protected Reservation _PersonReservation ;
public Reservation PersonReservation {
get { return _PersonReservation ; }
set {
_PersonReservation = value;
NotifyPropertyChanged("PersonReservation ");
}
}
protected Room _PersonRoom ;
public Room PersonRoom {
get { return _PersonRoom ; }
set {
_PersonRoom = value;
NotifyPropertyChanged("PersonRoom");
}
}
protected Person _myPerson ;
public Person myPerson {
get { return _myPerson ; }
set {
_myPerson = value;
NotifyPropertyChanged("myPerson ");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged( string PropertyName ) {
if ( PropertyChanged != null ) {
PropertyChanged( this, new PropertyChangedEventArgs( PropertyName ) );
}
}
}
In the MainWindows you can assign and use the dataContext :
public partial class MainWindow : Window {
DC_Reservation dataContext {
get { return DataContext as DC_Reservation; }
}
private string message;
public MainWindow() {
InitializeComponent();
DataContext = new DC_Reservation();
}
protected void Create_Reservation_Click(object sender, RoutedEventArgs e) {
dataContext.PersonReservation = new Reservation();//Create a reservation instance
dataContext.PersonRoom = new Room(); //Create an instance of a room
dataContext.myPerson = new Person();//Create an instance of a person
CreateResRoom createReservationRoom = new CreateResRoom();//Create a instance of the CreateReservation WPF Form
// I'm not sure whether the next line is required.
createReservationRoom.DataContext = DataContext;
createReservationRoom.Show();
}
}
You can assign the DataContext in the constructor, but I think the better way is to define the DataContext in the MainWindow, in the other windows you can use the DesignContext:
<Window.DataContext>
<local:DC_Reservation />
</Window.DataContext>
So you can use the same DataContext over all forms ...
With DataBindings you can bind the input to the field:
<TextBox Text="{Binding FirstName, Path=myPerson, Mode=TwoWay}" />
I found another answer that Zarathos posted Jan 16 '13 at 21:43
for a different question
Use a public static class and access it from anywhere.
public static class Globals
{
public static String s_Name = "Mike"; //Modifiable in Code
public const int32 VALUE = 10; // unmodifiable
}
Then you can use it anywhere, provided you are working on the same namespace
string name = Globals.s_Name;
I need to change the WPF label content within a process,
I tried this but no content change in real time.
where am I doing wrong?
Event caller:
private void connect_button_MouseDown(object sender, MouseButtonEventArgs e)
{
Mouse.OverrideCursor = Cursors.Wait;
labelStstusUpdate("Connecting.."); // Status changer
config = new Configuration();
bool status = config.connectViaUSB();
Mouse.OverrideCursor = null;
if (!status)
{
labelStstusUpdate("Disconnected");// Status changer
}
else
{
labelStstusUpdate("Connected");// Status changer
}
}
Status changer method:
private void labelStstusUpdate(string message)
{
Dispatcher.BeginInvoke(DispatcherPriority.Background, (SendOrPostCallback)delegate
{
available_amount_label.Content = message;
}, null);
}
This is an code from my recent application where we are changing the value of label in runtime try to find a workaround from this
public partial class MainWindow : Window
{
int Value=0;
private delegate void UpdateMyLabel(System.Windows.DependencyProperty dp, Object value);
private void Processmerge()
{
UpdateMyLabel updateLabelDelegate = new UpdateMyLabel(_Mylabel.SetValue);
foreach (var item in Collections)
{
string _Mylabel= "Process completed..." + Value.ToString() + " %";
Dispatcher.Invoke(updateLabelDelegate, System.Windows.Threading.DispatcherPriority.Background, new object[] { System.Windows.Controls.Label.ContentProperty, _Mylabel});
Value++;
}
}
}
}
You cannot do that in WPF - the Databinding is totally different.
Basically, you have to set the Datacontext of the Window to your class and then bind the Label to a property on your class.
This would look like:
public class MyWindow()
{
public string Labeltext{ get; set; }
private void labelStstusUpdate(string message)
{
this.Labeltext = message
this.NotifyOfPropertyChange(() => this.Labeltext);
}
}
When you call the Notify Method, WPF will notice the change and update the label.
As a hint: Use a mvvm framework like Caliburn.Micro for WPF design, it drasticalls reduces the amount of errors and eases the development a bit.
I'm studying design patterns right now, I'm fairly new to this model-view-presenter, although I have already experience in asp.net mvc I'm trying to do an implementation of mvp in winforms.
The string in the textbox will be sorted with an algorithm based on the combobox. Right now when I click the button it throws a null reference exception
Here is the UI:
Here are my classes and codes:
class FormPresenter
{
private ISortingView _view;
private string _algorithm;
private StringToSortModel sortMe = new StringToSortModel();
public FormPresenter(ISortingView view)
{
_view = view;
_view.sortTheString += view_sortString;
sortMe.sortThis = view.stringToSort;
_algorithm = _view.algorithm;
//Algorithm = view.stringToSort;
//sortingform.sortTheString += (obj
}
private void view_sortString(object sender, EventArgs e)
{
SortContext context = new SortContext();
_view.sortedText = context.Sort(sortMe.sortThis.ToCharArray());
}
}
interface ISortingView
{
event EventHandler sortTheString;
string stringToSort { get; }
string algorithm { get; }
string sortedText { get; set; }
}
public partial class SortingForm : Form, ISortingView
{
public SortingForm()
{
InitializeComponent();
comboBox1.Items.Add("Bubble Sort");
comboBox1.Items.Add("Insertion Sort");
comboBox1.SelectedItem = "Bubble Sort";
textBox1.Text = "Emiri";
}
public event EventHandler sortTheString;
public string algorithm { get { return comboBox1.SelectedItem.ToString(); } }
public string stringToSort { get { return textBox1.Text; } }
public string sortedText { get { return label2.Text; } set { label2.Text = value; } }
private void Form1_Load(object sender, EventArgs e)
{
}
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
//char[] x = textBox1.Text.ToCharArray();
//SortContext con = new SortContext();
//con.SetSortStrategy(new InsertionSort());
//label2.Text = con.Sort(x);
//if(sortString != null)
//{
//this prodcues a null exception error
sortTheString(sender, e);
//}
}
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var mainForm = new SortingForm();
var presenter = new FormPresenter(mainForm);
Application.Run(new SortingForm());
}
}
I have not included the codes for the model and the classes the contains the sorting functions to keep this post short. The problem I have is that when button is clicked it throws a null reference exception error, something that I have been stuck on for hours already.
Sir/Ma'am your answers would be of great help. Thank you++
Your null is coming from this line
sortTheString(sender, e);
because you are not using the same form instance in your Presenter. Change to this in your main...
Application.Run(mainForm);
The event handler does not have any subscribers (because of the Application.Run(new SortingForm()); C# will treat that as null rather than an empty subscriber list.
ISortingView mainForm = new SortingForm();
var presenter = new FormPresenter(mainForm);
Application.Run(mainForm as Form);
I'm trying to use the FolderBrowserDialog from my WPF application - nothing fancy. I don't much care that it has the Windows Forms look to it.
I found a question with a suitable answer (How to use a FolderBrowserDialog from a WPF application), except I'm using MVVM.
This was the answer I "implemented", except I can't get the window object and I'm just calling ShowDialog() without any parameters.
The problem is this:
var dlg = new FolderBrowserDialog();
System.Windows.Forms.DialogResult result = dlg.ShowDialog(this.GetIWin32Window());
In my ViewModel there the this has no GetIWin32Window() method for me to get the Window context.
Any ideas on how to make this work?
First, you could use the ShowDialog signature that does not require a window.
var dlg = new FolderBrowserDialog();
DialogResult result = dlg.ShowDialog();
Second, you could send the main window of the Application as the owning window.
var dlg = new FolderBrowserDialog();
DialogResult result = dlg.ShowDialog(Application.Current.MainWindow.GetIWin32Window());
The second option might not be considered very MVVMish.
See the answer by #Dr. ABT in this question for a way to inject a pointer to your View into your ViewModel (not sure if this is a good idea or a bad idea, but I'm not going to let that stop me) With this technique, you would have access in your VM to the corresponding View if you really want to make that View be the owner of the FolderBrowserDialog.
#ChrisDD is right about defining an interface and wrapping FolderBrowserDialog. That is how we do it:
public interface IFolderBrowserDialog
{
string Description { get; set; }
Environment.SpecialFolder RootFolder { get; set; }
string SelectedPath { get; set; }
bool ShowNewFolderButton { get; set; }
bool? ShowDialog();
bool? ShowDialog(Window owner);
}
//Decorated for MEF injection
[Export(typeof(IFolderBrowserDialog))]
[PartCreationPolicy(CreationPolicy.NonShared)]
internal class WindowsFormsFolderBrowserDialog : IFolderBrowserDialog
{
private string _description;
private string _selectedPath;
[ImportingConstructor]
public WindowsFormsFolderBrowserDialog()
{
RootFolder = System.Environment.SpecialFolder.MyComputer;
ShowNewFolderButton = false;
}
#region IFolderBrowserDialog Members
public string Description
{
get { return _description ?? string.Empty; }
set { _description = value; }
}
public System.Environment.SpecialFolder RootFolder { get; set; }
public string SelectedPath
{
get { return _selectedPath ?? string.Empty; }
set { _selectedPath = value; }
}
public bool ShowNewFolderButton { get; set; }
public bool? ShowDialog()
{
using (var dialog = CreateDialog())
{
var result = dialog.ShowDialog() == DialogResult.OK;
if (result) SelectedPath = dialog.SelectedPath;
return result;
}
}
public bool? ShowDialog(Window owner)
{
using (var dialog = CreateDialog())
{
var result = dialog.ShowDialog(owner.AsWin32Window()) == DialogResult.OK;
if (result) SelectedPath = dialog.SelectedPath;
return result;
}
}
#endregion
private FolderBrowserDialog CreateDialog()
{
var dialog = new FolderBrowserDialog();
dialog.Description = Description;
dialog.RootFolder = RootFolder;
dialog.SelectedPath = SelectedPath;
dialog.ShowNewFolderButton = ShowNewFolderButton;
return dialog;
}
}
internal static class WindowExtensions
{
public static System.Windows.Forms.IWin32Window AsWin32Window(this Window window)
{
return new Wpf32Window(window);
}
}
internal class Wpf32Window : System.Windows.Forms.IWin32Window
{
public Wpf32Window(Window window)
{
Handle = new WindowInteropHelper(window).Handle;
}
#region IWin32Window Members
public IntPtr Handle { get; private set; }
#endregion
}
Then we make the VM/Command where we want to use the FolderBrowser import IFolderBrowserDialog. In application, IFolderBrowserDialog.ShowDialog shows the dialog. In unit test, we mock IFolderBrowserDialog so we can verify that it was called with correct parameters and/or send the selected folder back to the sut so that the test can continue.
If you're determined to use FolderBrowserDialog, I'd use this kind of design.
First, create a DependencyProperty on your View to expose its handle.
public static readonly DependencyProperty WindowHandleProperty =
DependencyProperty.Register("WindowHandle", typeof(System.Windows.Forms.IWin32Window), typeof(MainWindow), new PropertyMetadata(null));
// MainWindow.cs
public System.Windows.Forms.IWin32Window WindowHandle
{
get { return (System.Windows.Forms.IWin32Window)GetValue(WindowHandleProperty); }
set { SetValue(WindowHandleProperty, value); }
}
Now, when your window loads, you can retrieve the handle using the extensions provided in the question you linked to:
// MainWindow.cs
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var binding = new Binding();
binding.Path = new PropertyPath("WindowHandle");
binding.Mode = BindingMode.OneWayToSource;
SetBinding(WindowHandleProperty, binding);
WindowHandle = this.GetIWin32Window();
}
So, you are binding one-way to source using a "WindowHandle" property. So if your ViewModel has a WindowHandle property, it will be kept up to date with the valid IWin32Handle for the related view:
// ViewModel.cs
private System.Windows.Forms.IWin32Window _windowHandle;
public System.Windows.Forms.IWin32Window WindowHandle
{
get
{
return _windowHandle;
}
set
{
if (_windowHandle != value)
{
_windowHandle = value;
RaisePropertyChanged("WindowHandle");
}
}
}
This is a good solution because you're not hard-coding one ViewModel to be paired with one specific View. If your use multiple Views with the same ViewModel, it should just work. If you create a new View but you don't implement the DependencyProperty, it will just operate with a null handle.
EDIT:
As a side note, have you actually tested just not providing an IWin32Owner parameter? For me, it still automatically opens as a modal dialog for the application and blocks the user from interacting with all of the application's windows. Is there something else you need it to do instead?
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
MVVM way:
define a new interface for FolderBrowserDialog. Create a new class & implement that interface. (Implementing is done with actual FolderBrowserDialog class).
This way you will not tie MVVM to specific implementation and the actual logic can be later tested.
To handle any kind of dialog stuff within the mvvm pattern, you should go with a kind of Dialog-Service. In this post you will find some hints to go with this approach.
Putting dialog stuff into a service keeps the mvvm pattern untouched. The service takes care of all the creation of the dialogs and can provide the results. The view-model just calls methods and subscribes events provided by a service.
if you use dependency injection for the service (interface), you get the advantage to keep you solution testable by mocking. Or you could replace the forms folderbrowserdialog if there will be a wpf one.
It's handy to use Behaviors in this case. Add a dependency property, and you can use that to bind the value from the dialog to a property in your viewmodel.
public class FolderBrowserDialogBehavior : Behavior<System.Windows.Controls.Button>
{
/// <summary>
/// Dependency Property for Path
/// </summary>
public static readonly DependencyProperty PathProperty =
DependencyProperty.Register(nameof(Path), typeof(string), typeof(FolderBrowserDialogBehavior));
/// <summary>
/// Property wrapper for Path
/// </summary>
public string Path
{
get => (string) this.GetValue(PathProperty);
set => this.SetValue(PathProperty, value);
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Click += OnClick;
}
protected override void OnDetaching()
{
AssociatedObject.Click -= OnClick;
}
/// <summary>
/// Triggered when the Button is clicked.
/// </summary>
private void OnClick(object sender, RoutedEventArgs e)
{
using (var dialog = new FolderBrowserDialog())
{
try
{
if (dialog.ShowDialog() == DialogResult.OK)
{
FilePath = dialog.SelectedPath;
}
}
catch (Exception ex)
{
//Do something...
}
}
}
}
In the view;
<Button ...>
<i:Interaction.Behaviors>
<behaviors:FolderBrowserDialogBehavior FilePath="{Binding Path=SomePropertyInViewModel, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
</Button>