I have a button in an MVVM application that is hooked up to a command in the view model. The handler for the view model command does some file I/O (particularly calling File.Copy to create or overwrite an existing file).
Now, it seems that when I double-click on the button, this command handler gets called twice. Since both handlers are now trying to access the same file to copy/overwrite it at the same time, I'm getting an IOException.
Is there anyway to deal with this situation short of catching the IOException and ignoring it? This does not seem to be a guaranteed catch although there may be unrelated problems with the rest of the system that causes that to happen.
Use a value in the the ViewModel to protect the code that would be running when a click occurs. Set a value like: bool bFileIO = false;
Then in your handler function:
if (!bFileIO)
{
bFileIO = true;
//file IO here
bFileIO = false;
}
Something like that would protect the multi-clicking from trying to run multiple times.
The easiest way of doing this is to have your command return false in CanExecute while you're executing. This will prevent the second click from happening (as your button will be disabled). If using a DelegateCommand from prism:
private readonly DelegateCommand<object> copyCommand;
private bool isCopying = false;
public MyViewModel()
{
copyCommand = new DelegateCommand<object>(
_ => !isCopying,
_ =>
{
if (isCopying) return; // this shouldn't be required, but here for safety
isCopying = true;
copyCommand.RaiseCanExecuteChanged();
// do copy
isCopying = false;
copyCommand.RaiseCanExecuteChanged();
});
}
I think you should use the CanExecute of your command to control your button.
<Button Command="{Binding WriteFileCommand}" Content="Button" Height="23" HorizontalAlignment="Left" Margin="273,194,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
and the viewmodel
public class MyViewModel
{
private bool isWritingFile = false;
public DelegateCommand WriteFileCommand
{
get;
private set;
}
public bool IsWritingFile
{
get
{
return isWritingFile;
}
set
{
isWritingFile = value;
WriteFileCommand.RaiseCanExecuteChanged();
}
}
public MyViewModel()
{
WriteFileCommand = new DelegateCommand(WriteFile, CanWriteFile);
}
private void WriteFile()
{
IsWritingFile = true;
// write the file
// FileStream stream = new FileStrem(...)
//
IsWritingFile = false;
}
}
Related
My page within a frame takes to time to load meaning the controls take some time to appear on the page for the first time. Where in my main window.cs file should I set the IsBusy = true.I have no idea how to use busy indicator.When should I switch it to true or false. please guide me how should I use it ? Thanks in advance.
Generally you would set the busy indicator before you start doing a load of heavy processing so that is dependant on your code.
It would normally before just before you spawn a background thread to do a load of work leaving the UI to say it's busy at the moment and when the thread is completing then "unbusy" the UI.
Wrap you Xaml with a busy indicator . Assuming you are using MVVM
<xctk:BusyIndicator BusyContent="{Binding BusyText}" IsBusy="{Binding IsBusy}">
<Grid>
<!--Your controls and content here-->
</Grid>
</xctk:BusyIndicator>
In your viewmodel
/// <summary>
/// To handle the Busy Indicator's state to busy or not
/// </summary>
private bool _isBusy;
public bool IsBusy
{
get
{
return _isBusy;
}
set
{
_isBusy = value;
RaisePropertyChanged(() => IsBusy);
}
}
private string _busyText;
//Busy Text Content
public string BusyText
{
get { return _busyText; }
set
{
_busyText = value;
RaisePropertyChanged(() => BusyText);
}
}
Command and Command Handler
//A Command action that can bind to a button
private RelayCommand _myCommand;
public RelayCommand MyCommand
{
get
{
return _myCommand??
(_myCommand= new RelayCommand(async () => await CommandHandler(), CanExecuteBoolean));
}
}
internal async Task CommandHandler()
{
Isbusy = true;
BusyText = "Loading Something...";
Thread.Sleep(3000); // Do your operation over here
Isbusy = false;
}
I have a method defined in the Model that would execute a long running script where I want to capture the output message when the script is in progress and output to the View via the ViewModel. I understand in order to get realtime update of the output message I should run the Model method in a backgroundworker and raise its ReportProgress event when it has output message to report in order to run the UI update and the script on two separate threads. The problem I have is the backgroundworker object is defined in the ViewModel, so using it to call the Model method is straight forward, but how do I raise the ReportProgress event from the Model method? The only way I can think of is passing in the backgroundworker as input parameter into the method but I feel uneasy about this. Can anyone tell me if this is the right approach in implementing the MVVM framework?
Here are my code stripped to the most bare bone. In my View xaml I have a TextBox bind to the Logger property and DeployCommand command in my ViewModel:
<TextBox Grid.Row="1 " Name="txtOutput" MinHeight="40"
Text="{Binding Logger}"
IsReadOnly="True" Margin="10,10" VerticalScrollBarVisibility="Auto"
IsEnabled="True" MaxLines="2000" TextWrapping="WrapWithOverflow"/>
<Button x:Name="BtnDeploy"
Command="{Binding DeployCommand}"
Content="Deploy"
Height="23"
Margin="5,2"
HorizontalAlignment="Right"
Width="125"
FontFamily="Kalinga"
AutomationProperties.AutomationId="DeployButton"/>
In my ViewModel, the DeployCommand command will trigger the method OnDeploy which in turn will call the Deploy method in Model using the backgroundworker object:
private string logger = string.Empty;
public string Logger
{
get { return logger; }
set
{
logger = value;
RaisePropertyChanged("Logger");
}
}
public ICommand DeployCommand { get; private set; }
public MainWindowViewModel()
{
_worker = new BackgroundWorker()
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_worker.DoWork += worker_DoWork;
// _worker.RunWorkerCompleted += worker_RunWorkerCompleted;
_worker.ProgressChanged += worker_ProgressChanged;
DeployController = new DeploymentModel();
this.DeployCommand = new DelegateCommand<object>(this.OnDeploy);
}
private void OnDeploy(object obj)
{
Logger += #"Offline Deployment Started" + System.Environment.NewLine;
if (!_worker.IsBusy)
{
_worker.RunWorkerAsync(DeployController);
}
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
var worker = (BackgroundWorker)sender;
var deployModel = (DeploymentModel)e.Argument;
deployModel.Deploy(script);
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Logger += e.UserState.ToString();
}
Finally in the Model:
public bool Deploy(string ScriptFile)
{
bool Success = true;
string strCmdText = string.Format(#"/c ""{0}""", ScriptFile);
try
{
var startInfo = new ProcessStartInfo
{
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = kitFolder,
UseShellExecute = false,
RedirectStandardError = true,
RedirectStandardOutput = true,
FileName = "cmd.exe",
CreateNoWindow = true,
Arguments = strCmdText,
};
// Launch shell command to run powersheel script
using (Process myProcess = Process.Start(startInfo))
{
// capturing script output message
myProcess.OutputDataReceived += (s, e) =>
{
LogMessage("ExecuteDeploymentKit: " + e.Data);
};
myProcess.ErrorDataReceived += (s, e) =>
{
Success = false;
LogMessage("ExecuteDeploymentKit: ! > " + e.Data);
};
myProcess.BeginErrorReadLine();
myProcess.BeginOutputReadLine();
System.Threading.Thread.Sleep(5000);
myProcess.WaitForExit();
}
}
catch (Exception ex)
{
LogMessage("ExecuteDeploymentKit: " + ex.Message);
return false;
}
if (Success)
{
LogMessage("ExecuteDeploymentKit: Offline Deployment Kit executed successfully");
}
else
{
LogMessage("ExecuteDeploymentKit: Offline Deployment Kit failed");
}
return Success;
}
I have added workder_ProgressChanged to handle the ProgressChanged event of the backgroundworker in order to update the View in the UI thread but without the backgroundworker object in my Model, I can't raise the ProgressChanged event from the method Deploy()
Thanks
The standard way would be for your VM to implement the IProgress interface and pass your M the VM cast as an IProgress object. You shouldn't pass it the VM since that could be a reference nightmare.
But really, the background worker should be implemented in the VM, not the M. And you shouldn't use BackgroundWorker anymore and move onto the new async methods.
If I understand your question right, you might be breaking core principles of MVVM by letting the Model drive your viewmodel and view. Without really having much to go off of, I would suspect that the best approach to this would be to actually create a "service".
Keep your model dumb and let it only contain data. Think POCO. Then, utilize a service that implements a background worker. Have your View Model run the service. The View model can call the service and provide that service a reference to your instantiated model. This way, you aren't heavily coupling your model to your view model.
I've abandoned the MVVM midway through app development just to get this app out.
I've written a method in the code behind to update the database/datagrid etc.
My application navigation is using Commands to the ViewModel firing some event but never touches the code-behind except one time to initialize the class.
So basically I push the button one time and it works with the default initial setting but I can't call my code-behind Update() method anymore once the view as been intialized.
How can I call this code-behind method from the view model?
Thanks!!
Update code
//Navigation ViewModel
//PaneVm.cs
public CommandExtension NewAssignmentCommand { get; set; }
private void CreateCommands()
{
NewAssignmentCommand = new CommandExtension(NewAssignment, CanNewAssignment);
}
GlobalCommands.NewAssignmentCommand = NewAssignmentCommand;
private bool CanNewGroupAssignment(object obj)
{
return true;
}
private void NewGroupAssignment(object obj)
{
OnPropertyChanged("NewGroupAssignmentCommand");
}
//MainVM.cs
// [Events]
void _PaneVm_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "NewGroupAssignmentCommand")
WorkspaceVm.CurrentVm = new NewAssignmentsVm();
}
//NewAssignmentVm.cs
//Constructor
public NewAssignmentsVm()
{
var rc = new RepositoryContext();
_RoResearchers = new ObservableCollection<Researcher>(rc.ResearcherData.GetAllResearchers());
_QuarterDateTime = DateTime.Now;
CreateCommands();
}
//NewAssignment.cs
//Code-behind
//The method
private void UpdateGrid()
{
report_datagrid.ItemsSource = null;
using (var rc = new RepositoryContext())
{
if (quarter_datepicker.SelectedDate != null)
{
if (!String.IsNullOrEmpty(reportType))
researchers = rc.ResearcherData.GetResearchersWeeksByQuarter(Convert.ToDateTime(quarter_datepicker.SelectedDate), reportType).ToList();
}
}
}
UPDATE 2:
I solved my problem based off this answer. I created a Global Action
public static class GlobalCommands
{
public static Action UpdateGrid { get; set; }
}
Then in my code-behind constructor I set the value public
MyCodeBehind()
{
GlobalCommands.UpdateGrid = new Action(() => this.UpdateGrid());
}
Didn't need to bind to the context again. Everything else was the same. Thank you
Main idea is:
class MyCodeBehind
{
public MyCodeBehind()
{
Action action = new Action(()=> this.SomeMethodIWantToCall());
var myVM = new MyVM(action); // This is your ViewModel
this.DataContext = myVM;
}
private void SomeMethodIWantToCall(){...}
}
class MyVM
{
private Action action;
public MyVM(Action someAction)
{
this.action = someAction;
}
private void SomeMethodInVM()
{
this.action(); // Calls the method SomeMethodIWantToCall() in your code behind
}
}
Instead of letting code-behind know about viewmodel, You can make use of NotifyOnSourceUpdated in your xaml binding.
Something like this:
<TextBlock Grid.Row="1" Grid.Column="1" Name="RentText"
Text="{Binding Path=Rent, Mode=OneWay, NotifyOnTargetUpdated=True}"
TargetUpdated="OnTargetUpdated"/>
Here, 'OnTargetUpdated' is a handler in your code behind. This handler will be invoked when "Rent" property of ViewModel is changed.
Details at MSDN
I have been reading Mark Seeman's book on dependency injection in .NET and I'm struggling to configure composition root in WPF application.
My container will be registered in the application startup method:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var container = new Container();
container.Configure(r =>
{
r.For<IAccountServices>().Use<AccountServicesProxy>();
r.For<MainWindow>().Use<MainWindow>();
});
}
This makes sense as the application startup represents my composition root.
WPF windows in my application are based on view models. View models use constructor injection. E.g. I may compose a view model by injecting implementation of IAccountServices.
When it comes to creating my main window, I can do the following inside of the OnStartup method:
var mainWindow = container.GetInstance<MainWindow>();
mainWindow.Show();
Once I'm inside of the main window, I might want open up another window. So far I've been able to come up with one way of doing this, which is to create a window factory and ask window factory to resolve instance of the window. I'll have to make sure that window factory is available in every view model that might need to open a new window. In my mind this is as bad as passing IoC container around my application (service locator anti-pattern comes to mind).
Does this approach seem right to you? My gut feeling tells me that this is wrong, but I haven't come up with a better way of achieving this (yet).
I think before implement patterns of behavior, such as a Mediator, and the like, need to decide on a generic pattern for easy application structure. For this purpose, namely, for the create independent windows, well suited Abstract factory pattern.
Creation of the windows can be implemented on the side ViewModel using methods such as IDialogService. But I think that this task should be implemented on the side View, because the Window object refers to the View and not to ViewModel. So, you must create MVVM style architecture that it allows create independent windows using design patterns.
I created a project in which an Abstract factory creates a Window on the side of the View using the attached behavior. Abstract factory also implements the Singleton pattern to create a global point of access and to ensure the uniqueness of the newly constructed object. Attached behavior implicitly implements pattern Decorator who is a wrapper for an abstract factory that is used on the side of XAML. To an Abstract factory does not refer to objects which are located in ViewModel is used a Proxy pattern which is a ContentControl with DataTemplate without DataType. Also used Command pattern for independent action between objects. As a result, this project uses the following patterns:
Abstract factory
Singleton
Decorator
Proxy
Command
The project structure looks like this:
In the attached behavior has attached dependency property Name, which is transmitted in the name of the new window. For him registered PropertyChangedEvent, which is a call Make method an abstract factory:
private static void IsFactoryStart(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var window = sender as Window;
if (window == null)
{
return;
}
if (e.NewValue is String && String.IsNullOrEmpty((string)e.NewValue) == false)
{
_typeWindow = (string)e.NewValue;
if (_typeWindow != null)
{
var newWindow = WindowFactory.Instance.Make(_typeWindow);
newWindow.Show();
}
}
}
WindowFactory together with the Singleton pattern looks like this:
public class WindowFactory : IWindowFactory
{
#region WindowFactory Singleton Instance
private static WindowFactory _instance = null;
private static readonly object padlock = new object();
public static WindowFactory Instance
{
get
{
lock (padlock)
{
if (_instance == null)
{
_instance = new WindowFactory();
}
return _instance;
}
}
}
#endregion
public Window Make(string TypeWindow)
{
if (TypeWindow.Equals("WindowOneViewProxy"))
{
var windowOne = new Window();
windowOne.Width = 450;
windowOne.Height = 250;
windowOne.WindowStartupLocation = WindowStartupLocation.CenterScreen;
windowOne.Title = TypeWindow;
windowOne.ContentTemplate = Application.Current.Resources[TypeWindow] as DataTemplate;
return windowOne;
}
else if (TypeWindow.Equals("WindowTwoViewProxy"))
{
var windowTwo = new Window();
windowTwo.Width = 500;
windowTwo.Height = 200;
windowTwo.WindowStartupLocation = WindowStartupLocation.CenterScreen;
windowTwo.Title = TypeWindow;
windowTwo.ContentTemplate = Application.Current.Resources[TypeWindow] as DataTemplate;
return windowTwo;
}
else if (TypeWindow.Equals("WindowThreeViewProxy"))
{
var windowThree = new Window();
windowThree.Width = 400;
windowThree.Height = 140;
windowThree.WindowStartupLocation = WindowStartupLocation.CenterScreen;
windowThree.Title = TypeWindow;
windowThree.ContentTemplate = Application.Current.Resources[TypeWindow] as DataTemplate;
return windowThree;
}
else
throw new Exception("Factory can not create a: {0}" + TypeWindow);
}
}
For the property Window.ContentTemplate set DataTemplate from resources. ContentTemplate is responsible for the visual representation, in order to bind properties from ViewModel, you need to set the object to Content. But in this case, the Abstract factory reference will to ViewModel, and to avoid them and using the proxy pattern as follows:
WindowOneProxyView
<DataTemplate x:Key="WindowOneViewProxy">
<ContentControl ContentTemplate="{StaticResource WindowOneViewRealObject}">
<ViewModels:WindowOneViewModel />
</ContentControl>
</DataTemplate>
WindowOneViewRealObject
<DataTemplate x:Key="WindowOneViewRealObject" DataType="{x:Type ViewModels:WindowOneViewModel}">
<Grid>
<Label Content="{Binding Path=WindowOneModel.TextContent}"
HorizontalAlignment="Center"
VerticalAlignment="Top"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Background="Beige" />
<Button Content="One command"
Width="100"
Height="30"
HorizontalAlignment="Center"
Command="{Binding OneCommand}" />
</Grid>
</DataTemplate>
In DataTemplate proxy is not specified DataType, but it is in the real object.
In MainViewModel has commands to simply set the window name, which will give input for attached behavior:
MainModel
public class MainModel : NotificationObject
{
#region TypeName
private string _typeName = null;
public string TypeName
{
get
{
return _typeName;
}
set
{
_typeName = value;
NotifyPropertyChanged("TypeName");
}
}
#endregion
}
MainViewModel
public class MainViewModel
{
#region MainModel
private MainModel _mainModel = null;
public MainModel MainModel
{
get
{
return _mainModel;
}
set
{
_mainModel = value;
}
}
#endregion
#region ShowWindowOneCommand
private ICommand _showWindowOneCommand = null;
public ICommand ShowWindowOneCommand
{
get
{
if (_showWindowOneCommand == null)
{
_showWindowOneCommand = new RelayCommand(param => this.ShowWindowOne(), null);
}
return _showWindowOneCommand;
}
}
private void ShowWindowOne()
{
MainModel.TypeName = "WindowOneViewProxy";
}
#endregion
#region ShowWindowTwoCommand
private ICommand _showWindowTwoCommand = null;
public ICommand ShowWindowTwoCommand
{
get
{
if (_showWindowTwoCommand == null)
{
_showWindowTwoCommand = new RelayCommand(param => this.ShowWindowTwo(), null);
}
return _showWindowTwoCommand;
}
}
private void ShowWindowTwo()
{
MainModel.TypeName = "WindowTwoViewProxy";
}
#endregion
#region ShowWindowThreeCommand
private ICommand _showWindowThreeCommand = null;
public ICommand ShowWindowThreeCommand
{
get
{
if (_showWindowThreeCommand == null)
{
_showWindowThreeCommand = new RelayCommand(param => this.ShowWindowThree(), null);
}
return _showWindowThreeCommand;
}
}
private void ShowWindowThree()
{
MainModel.TypeName = "WindowThreeViewProxy";
}
#endregion
public MainViewModel()
{
MainModel = new MainModel();
}
}
MainWindow looks as:
<Window x:Class="WindowFactoryNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:WindowFactoryNamespace.ViewModels"
xmlns:AttachedBehaviors="clr-namespace:WindowFactoryNamespace.AttachedBehaviors"
AttachedBehaviors:WindowFactoryBehavior.Name="{Binding Path=MainModel.TypeName}"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="300" Width="300">
<Window.DataContext>
<this:MainViewModel />
</Window.DataContext>
<WrapPanel>
<Button Content="WindowOne"
Margin="10"
Command="{Binding ShowWindowOneCommand}" />
<Button Content="WindowTwo"
Margin="10"
Command="{Binding ShowWindowTwoCommand}" />
<Button Content="WindowThree"
Margin="10"
Command="{Binding ShowWindowThreeCommand}" />
</WrapPanel>
</Window>
Test View-ViewModel for the first window looks like this (they practically identical):
WindowOneModel
public class WindowOneModel : NotificationObject
{
#region TextContent
private string _textContent = "Text content for WindowOneView";
public string TextContent
{
get
{
return _textContent;
}
set
{
_textContent = value;
NotifyPropertyChanged("TextContent");
}
}
#endregion
}
WindowOneViewModel
public class WindowOneViewModel
{
#region WindowOneModel
private WindowOneModel _windowOneModel = null;
public WindowOneModel WindowOneModel
{
get
{
return _windowOneModel;
}
set
{
_windowOneModel = value;
}
}
#endregion
#region OneCommand
private ICommand _oneCommand = null;
public ICommand OneCommand
{
get
{
if (_oneCommand == null)
{
_oneCommand = new RelayCommand(param => this.One(), null);
}
return _oneCommand;
}
}
private void One()
{
WindowOneModel.TextContent = "Command One change TextContent";
}
#endregion
public WindowOneViewModel()
{
WindowOneModel = new WindowOneModel();
}
}
This project is available at this link.
Output
MainWindow
WindowOne
WindowTwo
WindowThree
IMHO, there is no need to over complicate the solution for the sake of MVVM purity. You risk the subsequent developers not understanding your elegant solution and break it. In fact there is a good chance of that as "pure" implementations tend to be not that readable because of the complexity.
IMHO, any solution where a problem is permanently solved under an abstraction with minimal code overhead and simplicity in its usage is better than doing considerable overhead every time the solution is used even if "purity" is achieved(it won't serve any purpose). The problem of showing dialog in the application has to be solved once and it should be easy to use it in the future.
Composing view models is perfectly fine, and could make life easier by allowing view models to interact without drama
A dialog service can be created which will act as a wrapper for all your dialog needs in the application. You can inject the Dialog Service and the child view models which needs to be displayed in a window, to your parent view model. When you need to display the window, ask the Dialog service to do it, passing it the view model instance and view name.
Note:code is not complied or tested
public class DialogService : IDialogService
{
IEventAggregator _eventAggregator;
bool _fatalError;
//Provides a wrapper function which will connect your view and view model and open a
//dialog
public Window ShowCustomDialog<TViewModel>(string name, TViewModel viewModel, bool
modal, double left, double top, Action<bool?> OnClose, int width, int height)
{
if (_fatalError == true)
{
return null;
}
Window view = new Window(name);
if (viewModel != null)
{
view.DataContext = viewModel;
}
if (left != -1.0 && top != -1.0)
{
view.WindowStartupLocation = WindowStartupLocation.Manual;
view.Left = left;
view.Top = top;
}
else
{
view.WindowStartupLocation = WindowStartupLocation.CenterScreen;
}
if (width != -1 && height != -1)
{
view.Width = width;
view.Height = height;
}
view.Closed += (o, e) =>
{
_eventAggregator.GetEvent<NotifyDialogAction>().Publish(false);
if (OnClose != null)
{
OnClose(e.DialogResult);
}
};
view.Loaded += (o, e) =>
{
_eventAggregator.GetEvent<NotifyDialogAction>().Publish(true);
Window window = o as Window;
if (window != null)
{
double dialogWidth = window.ActualWidth;
double screenWidth =
Application.Current.RootVisual.RenderSize.Width;
double dialogLeft = window.Left;
if (dialogLeft + dialogWidth > screenWidth)
{
window.Left = screenWidth - dialogWidth;
}
double dialogHeight = window.ActualHeight;
double screenHeight =
Application.Current.RootVisual.RenderSize.Height;
double dialogTop = window.Top;
if (dialogTop + dialogHeight > screenHeight)
{
window.Top = screenHeight - dialogHeight;
}
}
};
if (modal)
{
view.ShowDialog();
}
else
{
view.Show();
}
return view;
}
//Add more functions. For example to pop up a message box etc.
}
Usage
public class ComposedVM
{
public ViewModelA objA{get;set;}
public ViewModelB objB{get;set;}
IDialogService dialogService{get;set;}
public ComposedVM(ViewModelA a, ViewModelB b, IDialogService dlg )
{
objA = a;
objB = b;
dialogService = dlg
}
public void OnShowWindowACommand()
{
dialogService .ShowCustomDialog<object>(
DialogNames.ViewA/*view name constant*/, objA, true, -1.0, -1.0,
result =>
{
if (result == true)
{
dialogService.ShowMessageDialog(ApplicationStrings.SuccessFulOperation);
}
});
}
}
An event/message based communication can be used between modules. Using it for related view models in a module is an overkill IMHO.
Pushing container instance through constructor is a bad idea in 99% of cases, because container is a service locator. The main disadvantages of this approach are:
dependency from concrete implementation of container;
unclear API of your classes, which also leads to fragile unit tests.
There are many ways to create window in MVVM fashion:
using Mediators (like IMessenger in MvvmLight, IEventAggregator in Caliburn.Micro);
using special IDialogService;
using attached behaviours;
using Action that inserted via ViewModel constructor;
using Controllers.
Hi I try show busy indicator in shell which is wpf window.
In shell view I have this:
<Grid>
<extToolkit:BusyIndicator IsBusy="{Binding Path=ShellIsBusy, Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}"
BusyContent="{Binding Path=BusyMessage,Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}">
<ContentControl x:Name="ActiveItem" />
</extToolkit:BusyIndicator>
</Grid>
Shell model class is here:
[Export(typeof(IShellViewModel))]
public class ShellViewModel : Conductor<IScreen>.Collection.OneActive,
IShellViewModel, IPartImportsSatisfiedNotification
{
[Import]
internal IJinglePlayer JinglePlayer { get; set; }
private bool _isBusy;
private string _busyMessage;
public bool ShellIsBusy
{
get { return _isBusy; }
set
{
_isBusy = value;
NotifyOfPropertyChange(()=>ShellIsBusy);
}
}
public string BussyMessage
{
get { return _busyMessage; }
set
{
_busyMessage = value;
NotifyOfPropertyChange(()=>BussyMessage);
}
}
protected override void OnInitialize()
{
Show1();
base.OnInitialize();
JinglePlayer.PlayStartUp();
}
public void Show1()
{
var vm = IoC.Get<ILogOnViewModel>();
ActivateItem(vm);
}
public void Show2(IAccount account)
{
ActiveItem.Deactivate(true);
var vm = IoC.Get<IMeViewModel>();
vm.Account = account;
ActivateItem(vm); }
public void OnImportsSatisfied()
{
}
}
I run app, from active view model class I call this:
[Import]
internal IShellViewModel Shell { get; set; }
//...
Shell.ShellIsBusy = true;
Shell.BusyMessage = "logging";
//long task
Shell.Show2(logOnResult.ReturnValue);
Problem is that busy indicator is showed in the moment when is active another view.
I post my solution, maybe someone will have better idea. Problem is that long running task keep UI thread busy, so I call this task and shell method on active new view in another thread.
Something like this:
Task.Factory.StartNew(() => { //long task });
Task.Factory.StartNew(() => { Shell.Show2(...); });
This unblock UI thread and BusyIndicator can be displayed.