How to use a FolderBrowserDialog from a WPF application with MVVM - c#

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>

Related

Close/Hide Window from UserControl

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

Prism. Closing a dialog created with IDialogService

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.

Object loses data after initialization

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 :).

Updating label content within a method

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.

Binding Label to a static string

I have made a Base Form which is inherited by most Forms in the application. Base form contains a Status Bar Control that displays user name which is internally a static string. User can Switch User at any point in the application by pressing a button on status bar. At this point the user name in the status bar should also change, as if now it only changes in code and UI has no idea about the change. I have googled around and found that i need to bind the label with that static string by implementing a INotifyProperty Interface. I have implemented many example code without success.
Appreciate any help
use BindableAttribute for the property you want to bind a control to it.
[Bindable(true)]
public int Username {
get {
// Insert code here.
return 0;
}
set {
// Insert code here.
}
}
You must implement a class to notify prop changed and therefore the prop can not be static. Combine with a singleton pattern and you have yout solution.
public class Global : INotifyPropertyChanged
{
private string _userName;
public string UserName
{
get
{
return this._userName;
}
set
{
if (this._userName == value)
{
return;
}
this._userName = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("UserName"));
}
{
}
public event PropertyChangedEventHandler PropertyChanged;
private Global() {}
public static readonly Global Get = new Global();
}
Usage:
var currUserName = Global.Get.UserName;
Global.Get.PropertyChanged += (s, e) => Console.WriteLine(e.PropertyName);
Global.Get.UserName = "John";
And bind to Global.Get to property UserName.
I would:
1- Add a timer to the base form to update the status bar. (the timer resolution is uo to your requirement).
the timer Tick handler would be something like this:
private void timerStatusUpdate_Tick(object sender, EventArgs e)
{
toolStripStatusLabelMessage.Text = StatusMessage();
}
2 - Add a virtual StatusMessage method to your base class:
class BaseForm : Form
{
.......
public virtual string StatusMessage()
{
return "override me!";
}
}
3- override StatusMessage in all your derived classes
class XXXForm : BaseForm
{
........
public override string StatusMessage()
{
return "XXXForm status message";
}
}
I use Reactive Extensions for these things
For example if you have a Context class with a property UserName
you could do this
public static class Context
{
public static Subject<string> UserChanged = new Subject<string>();
private static string user;
public static string User
{
get { return user; }
set
{
if (user != value)
{
user = value;
UserChanged.OnNext(user);
}
}
}
}
And then on your forms just do
Context.UserChanged.ObserveOn(SynchronizationContext.Current)
.Subscribe(user => label.Text = user);
The ObserveOn(SynchronizationContext.Current) makes it safe for cross thread operation calls

Categories