I develop a wpf application on which I interact with a huge grid of buttons (built with an ItemsControl).
When I click on one button, the app displays a new window. This new window displays an UserControl.
I use a service to show the new window :
public class WindowService
{
#region Variable
#endregion Variable
#region Constructor
public WindowService()
{
}
#endregion Constructor
#region Properties
#endregion Properties
#region Public Method
public void ShowWindow(object viewModel)
{
var win = new WindowView();
//win.Content = viewModel; <-- not the best way, go to see in comment why (thanks #Ndubuisi Jr)
win.DataContext = viewModel;
win.Show();
}
#endregion Public Method
#region Private Method
#endregion Private Method
}
And the code to call this method :
public void display_InfoPoste(object commandParameter)
{
windowPoste = new WindowService();
windowPoste.ShowWindowCommandParameter(new InfoPosteViewModel(commandParameter));
}
No Problem with that. (The window displayed is only a content to receive different UserControl)
Now, I have a button "close" on the user control, but I don't find any way to close the window.
I work with MVVM pattern, that's why I don't find yet how to do that.
Could you help me?
(I can share a screenshot with you if you need)
Thanks a lot
Picture : Part of the project's arborescence
Just below, the requested code of the "InfoPosteViewModel.cs"
#region Variable
private string _commandParameter;
#endregion Variable
#region Constructor
public InfoPosteViewModel()
{
//FermerCommand = new RelayCommand(Action_FermerWindow);
}
public InfoPosteViewModel(object commandParameter)
{
SelectedViewModel = new InfoPosteViewModel();
_commandParameter = (string)commandParameter;
ID = _commandParameter;
}
#endregion Constructor
#region Properties
public ICommand FermerCommand { get; set; }
private static string _id;
public string ID
{
get { return _id; }
set
{
_id = value;
OnPropertyChanged("ID");
}
}
private object _selectedViewModel;
public object SelectedViewModel
{
get
{
return _selectedViewModel;
}
set
{
_selectedViewModel = value;
OnPropertyChanged("SelectedViewModel");
}
}
#endregion Properties
#region Public Method
public void Action_FermerWindow(object commandParameter)
{
}
#endregion Public Method
Try passing the close command to the VM that is being displayed in the Dialog. Then link that to the close button or action. Because it's the close command of the VM that opened the dialog you can then close the dialog using the reference in that VM and continue with any clean up or follow up code you need.
Oh, and you'll need to make the window an instance variable rather than a local variable.
The cleanest way to close a Window from its ViewModel in MVVM is using an attached property.
public static class perWindowHelper
{
public static readonly DependencyProperty CloseWindowProperty = DependencyProperty.RegisterAttached(
"CloseWindow",
typeof(bool?),
typeof(perWindowHelper),
new PropertyMetadata(null, OnCloseWindowChanged));
private static void OnCloseWindowChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
{
if (!(target is Window view))
return;
if (view.IsModal())
view.DialogResult = args.NewValue as bool?;
else
view.Close();
}
public static void SetCloseWindow(Window target, bool? value)
{
target.SetValue(CloseWindowProperty, value);
}
public static bool IsModal(this Window window)
{
var fieldInfo = typeof(Window).GetField("_showingAsDialog", BindingFlags.Instance | BindingFlags.NonPublic);
return fieldInfo != null && (bool)fieldInfo.GetValue(window);
}
}
This can be used in the View's xaml file
<Window
....
vhelp:perWindowHelper.CloseWindow="{Binding ViewClosed}" />
bound against the ViewClosed property (of type bool?) from the ViewModel. Setting this property value will close the View.
More details on my blog post
Related
I am trying to use a new IDialogService which was discussed in github issue 1666. A New IDialogService for WPF. I like this new feature but I can't find a solution for one case of using IDialogService in compare with InteractionRequest.
There is a button, pressing on which non-modal dialog is opened. If user press the same button one more time, while dialog still open, dialog close. How this behavior should be implemented in a proper way?
MainWindowViewModel
public class MainWindowViewModel : BindableBase
{
private readonly IDialogService _dialogService;
public DelegateCommand CustomPopupCommand { get; }
public MainWindowViewModel(IDialogService dialogService)
{
_dialogService = dialogService;
CustomPopupCommand = new DelegateCommand(OpenClosePopup);
}
private void OpenClosePopup()
{
// It looks like some additional logic should be implemented here.
// How to save previously opened IDialogAware instance and close it if needed?
_dialogService.Show("CustomPopupView", new DialogParameters("Title=Good Title"), result => { });
}
}
CustomPopupViewModel
public class CustomPopupViewModel : BindableBase, IDialogAware
{
private string _title;
public string Title
{
get => _title;
set => SetProperty(ref _title, value);
}
public DelegateCommand<object> CloseCommand { get; }
public CustomPopupViewModel()
{
CloseCommand = new DelegateCommand<object>(CloseDialog);
}
public event Action<IDialogResult> RequestClose;
public void OnDialogOpened(IDialogParameters parameters)
{
Title = parameters.GetValue<string>(nameof(Title));
}
public void OnDialogClosed()
{
}
public bool CanCloseDialog()
{
return true;
}
public void RaiseRequestClose(IDialogResult dialogResult)
{
RequestClose?.Invoke(dialogResult);
}
private void CloseDialog(object button)
{
RaiseRequestClose(
new DialogResult(button is ButtonResult buttonResult ? buttonResult : ButtonResult.Cancel));
}
}
I have no idea how can it be implemented in proper way because method IDialogService.Show() fully decoupled from knowing about ViewModel and View. Of course except the name of View.
You can always send an event through the event aggregator, probably you have to pass some id in the dialog parameters to close the right dialog if there's more than one open at a time.
But this feels really clunky, I'd prefer to get an IDisposable from Show/ShowDialog that closes the dialog on Dispose.
public CustomPopupViewModel(IEventAggregator eventAggregator)
{
eventAggregator.GetEvent<CloseDialogEvent>().Subscribe( id => { if (id == _id) CloseMe(); } );
}
public void OnDialogOpened(IDialogParameters parameters)
{
_id = parameters.GetValue<string>("id");
}
_dialogService.Show("CustomPopupView", new DialogParameters("id=12345"), result => { });
_eventAggregator.GetEvent<CloseDialogEvent>().Publish("12345");
I find it simplest to use Prism implementation of the subscriber pattern
I use a class that will be used in the pattern and is communicated:
public class DialogStatus
{
public bool DialogResult { get; set; }
}
In my sample, I show you how I do this using a Login Dialog in WPF using Prism 8.0.0.1909
in the App.cs
protected override void OnInitialized()
{
var login = Container.Resolve<LoginDialog>();
var result = login.ShowDialog();
if (result.HasValue && result.Value)
{
base.OnInitialized();
}
else
{
Application.Current.Shutdown();
}
}
in LoginDialog.cs in my Dialogs folder
public partial class LoginDialog : Window
{
public LoginDialog(IEventAggregator eventAggregator)
{
InitializeComponent();
eventAggregator.GetEvent<CloseDialogWindowEvent>().Subscribe(OnCloseWindow);
}
private void OnCloseWindow(DialogStatus obj)
{
base.DialogResult = obj.DialogResult;
}
}
now anywhere in my code, in a ViewModel of view a custom control's view model, the only thing I need to do is pass the IEventAggregator in in the constructor and save it in a field.
private readonly IEventAggregator _eventAggregator;
public LoginControlViewModel(IAuthenticationService authenticationService
, IConnectFileImporterService connectFileImporterService
, IDialogService dialogService
, IEventAggregator eventAggregator)
{
_eventAggregator= eventAggregator;
// the other code
}
I can now close my dialog, and in this sample return true to falls to my OnInitalize in my App.cs from anywhere by calling
_eventAggregator.GetEvent<CloseDialogWindowEvent>().Publish(new CloseDialogWindowEvent() { DialogResult = true });
or
_eventAggregator.GetEvent<CloseDialogWindowEvent>().Publish(new CloseDialogWindowEvent() { DialogResult = false});
If i understand correctly, you want to close the dailog window programmatically instead of clicking the windows's close button, right? If It is true, maybe I can provide you with a solution. Although this method is not very elegant, it is very simple.
My project use mahapps styles, I want use metrowindow as the dailoghost window. Following prism documentation, I register dialoghost window and usercontrol like this:
containerRegistry.RegisterDialogWindow<DialogHost>(nameof(DialogHost));
containerRegistry.RegisterDialog<UserEdit, UserEditViewModel>(nameof(UserEdit));
The UserEidt is a usercontrol, I place a confirm button and a cancel button in UserEidt, and both button binding DelegateCommand in UserEditViewModel. The question is, how can i close dailogwindow by clicking the cancel button?
Here is my solution, firstly define a IDailogViewModel interface:
public interface IDialogViewModel
{
Action CloseDialogWindow { get; set; }
}
Then UserEditViewModel implement this interface:
public class UserEditViewModel : BindableBase, IDialogAware,IDialogViewModel
{
public DelegateCommand CancelCmd { get; private set; }
public Action CloseDialogWindow { get; set; }
public UserEditViewModel()
{
CancelCmd = new DelegateCommand(CloseDialogWindow)
}
private void CloseDialogWindow()
{
CloseDialogWindow.Invoke();
}
}
Infact, when the dialog window popup, the UserEdit will be dialogWindow's content. So in the dialogwindow's loaded event handler, i can get the UserEdit object by using Window.Content, here is the code:
public partial class DialogHost : MetroWindow, IDialogWindow
{
public DialogHost()
{
InitializeComponent();
}
public IDialogResult Result { get; set; }
private void MetroWindow_Loaded(object sender, RoutedEventArgs e)
{
var dialogVM = (IDialogViewModel)((UserControl)Content).DataContext;
dialogVM.CloseDialogWindow += CloseDialogWindow;
}
void CloseDialogWindow()
{
Close();
}
}
Now,after clicking the cancel button, the dialogwindow will be close.
I have a WPF application which shows a Folder's contents in a Treeview in the MainWindowView. Now I have a new window where the user can change the location and press reload. Now I want to update the treeview in my MainWindowView as soon as the user presses the Reload button.
I am using an ObservableCollection object which is binded to the treeview. But I am not able to update the collection from the Change location window.
I want to know how to update the ObservableCollection of the MainWindowView from a different window. If I am doing any changes in the MainWindowView, then it immediately reflects in the TreeView
I am using MVVM architecture.
Is there any relationship between the MainWindow and the ChangeLocationWindow?
How does the ChangeLocationWindow show out, Show() or ShowDialog()? Check the following solution, any problems, let me know.
MainWindowViewModel:
public class MainWindowViewModel
{
public static MainWindowViewModel Instance = new MainWindowViewModel();
public ObservableCollection<string> Contents = new ObservableCollection<string>();
public string Location
{
get { return _location; }
set
{
if (_location != value)
{
_location = value;
ReloadContents();
}
}
}
private MainWindowViewModel()
{
}
private void ReloadContents()
{
// fill test data
Contents.Add("Some test data.");
}
private string _location;
}
MainWindowView:
{
public MainWindowView()
{
InitializeComponent();
MyListBox.ItemsSource = MainWindowViewModel.Instance.Contents;
var changeLocationWindow = new ChangeLocationWindow();
changeLocationWindow.Show();
}
}
ChangeLocationWindow:
public partial class ChangeLocationWindow : Window
{
public ChangeLocationWindow()
{
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MainWindowViewModel.Instance.Location = "Test";
}
}
The best approach to your problem is using Messaging pattern to send notifications to main viewmodel from another one about new changes.
Checkout the link for more details,
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 :).
Sorry to be cliche... but I'm pretty new to WPF and MVVM so I'm not sure how to handle this properly. I have a WinForms control within one of my views that I need to modify in it's code behind when an event is raised in the ViewModel. My view's datacontext is inherited so the viewmodel is not defined in the views constructor. How would I go about properly handling this? I am not using any frameworks with built in messengers or aggregators. My relevant code is below. I need to fire the ChangeUrl method from my ViewModel.
EDIT: Based on the suggestion from HighCore, I have updated my code. I am still not able to execute the ChangeUrl method however, the event is being raised in my ViewModel. What modifications need to be made??
UserControl.xaml
<UserControl ...>
<WindowsFormsHost>
<vlc:AxVLCPlugin2 x:Name="VlcPlayerObject" />
</WindowsFormsHost>
</UserControl>
UserControl.cs
public partial class VlcPlayer : UserControl
{
public VlcPlayer()
{
InitializeComponent();
}
public string VlcUrl
{
get { return (string)GetValue(VlcUrlProperty); }
set
{
ChangeVlcUrl(value);
SetValue(VlcUrlProperty, value);
}
}
public static readonly DependencyProperty VlcUrlProperty =
DependencyProperty.Register("VlcUrl", typeof(string), typeof(VlcPlayer), new PropertyMetadata(null));
private void ChangeVlcUrl(string newUrl)
{
//do stuff here
}
}
view.xaml
<wuc:VlcPlayer VlcUrl="{Binding Path=ScreenVlcUrl}" />
ViewModel
private string screenVlcUrl;
public string ScreenVlcUrl
{
get { return screenVlcUrl; }
set
{
screenVlcUrl = value;
RaisePropertyChangedEvent("ScreenVlcUrl");
}
}
WPF does not execute your property setter when you Bind the property, instead you must define a Callback method in the DependencyProperty declaration:
public string VlcUrl
{
get { return (string)GetValue(VlcUrlProperty); }
set { SetValue(VlcUrlProperty, value); }
}
public static readonly DependencyProperty VlcUrlProperty =
DependencyProperty.Register("VlcUrl", typeof(string), typeof(VlcPlayer), new PropertyMetadata(null, OnVlcUrlChanged));
private static void OnVlcUrlChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var player = obj as VlcPlayer;
if (obj == null)
return;
obj.ChangeVlcUrl(e.NewValue);
}
private void ChangeVlcUrl(string newUrl)
{
//do stuff here
}
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>