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.
Related
I have a UdpClient, firing off a DataRecevied event on my MainWindow:
public partial class MainWindow : Window
{
public static YakUdpClient ClientConnection = new YakUdpClient();
public ClientData;
public MainWindow()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
ClientData = new ClientData();
ClientConnection.OnDataReceived += ClientConnectionOnDataReceived;
}
private void ClientConnectionOnDataReceived(object sender, MessageEventArgs messageEventArgs)
{
ClientData.Users = messageEvenArgs.ConnectedUsers;
}
}
My ClientData and User classes look as follow:
public class ClientData
{
public List<User> Users {get;set;)
}
public class User
{
public string Name {get;set;}
}
On my MainWindow, I have a UserControl called UserListView which has a ViewModel called UserListViewModel
The ViewModel looks as follow:
public class UserListViewModel: BindableBase
{
public UserListViewModel()
{
//I am sure there are better ways of doing this :(
Users = new ObservableCollection<User>((MainWindow)Application.Current.MainWindow).ClientData.Users
});
private ObservableCollection<User> _users;
public ObservableCollection<User> Users
{
get{ return _users;}
set { this.SetProperty(ref this._users, value); }
}
}
The difficulty I have here, is when the ClientConnectionOnDataReceived event on the MainWindow gets fired, I would like to update my ClientData class, My Viewmodel should then somehow be notified that the list changed, and subsequently update my UI.
Can anyone give me a solid example of how to achieve this using MVVM (Prism) in WPF?
I am new to MVVM, so i am still trying to figure this out.
First of all, there's no obvious reason why the main window should do the subscription.
I'd go for something like this:
create a service that encapsulates the subscription (and subscribes in its constructor)
register that as a singleton
have it implement INotifyPropertyChanged (to notify consumers of a change to Users)
inject the service into UserListViewModel and observe the Users property (see PropertyObserver)
when Users in the service changes, update Users in the user list view model
and best of all, no need for ObservableCollection here :-)
EDIT: example:
interface IUserService : INotifyPropertyChanged
{
IReadOnlyCollection<User> Users
{
get;
}
}
class YakUdpService : BindableBase, IUserService
{
private readonly YakUdpClient _yakUdpClient;
private IReadOnlyCollection<User> _users;
public YakUdpService()
{
_yakUdpClient = new YakUdpClient();
_yakUdpClient.OnDataReceived += ( s, e ) => Users = e.ConnectedUsers;
}
public IReadOnlyCollection<User> Users
{
get
{
return _users;
}
private set
{
SetProperty( ref _users, value );
}
}
}
class UserListViewModel : BindableBase
{
private IReadOnlyCollection<UserViewModel> _users;
private readonly IUserService _userService;
private readonly PropertyObserver<IUserService> _userServiceObserver;
public UserListViewModel( IUserService userService )
{
_userService = userService;
_userServiceObserver = new PropertyObserver<IUserService>( userService );
_userServiceObserver.RegisterHandler( x => x.Users, () => Users = _userService.Users.Select( x => new UserViewModel( x ) ).ToList() );
// ^^^ should use factory in real code
}
public IReadOnlyCollection<UserViewModel> Users
{
get
{
return _users;
}
private set
{
SetProperty( ref _users, value );
}
}
}
and then register the service
Container.RegisterType<IUserService, YakUdpService>( new ContainerControlledLifetimeManager() );
in your bootstrapper or your module's initialization.
I have this Singleton that hold my ObservableCollection<MyData> as a memeber:
public sealed class Singleton
{
private static volatile Singleton instance;
private static object syncRoot = new Object();
public ObservableCollection<MyData> Files { get; private set; }
private Singleton()
{
Files = new ObservableCollection<MyData>();
}
public static Singleton Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
}
Declaration from main form class:
ObservableCollection<MyData> Files;
And here after the constructor:
Files= Singleton.Instance.Files;
XAML:
<ListView ItemsSource="{Binding Files}" />
Now when the user choose files i want to check each file:
private static void Check(IEnumerable<string> files)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
Task task = Task.Factory.StartNew(() =>
{
try
{
Parallel.ForEach(files,
new ParallelOptions
{
MaxDegreeOfParallelism = 1
},
file =>
{
ProcessFile(file);
});
}
catch (Exception)
{ }
}, tokenSource.Token,
TaskCreationOptions.None,
TaskScheduler.Default).ContinueWith
(t =>
{
}
, TaskScheduler.FromCurrentSynchronizationContext()
);
}
And:
private static void ProcessFile(string file)
{
// Lets assume that i want to add this file into my `ListView`
MyData data = new .....
Singleton.Instance.Files.Add(data);
}
So after this point when i am add files into my list nothing happenning.
Using your code above i was able to reproduce the issue you describe.
The problem is that WPF cannot bind to fields, see this question for more details. All you need to do is to change the ObservableCollection<MyData> in the code behind of your main form to a property instead of a field.
public partial class MainWindow : Window
{
public ObservableCollection<MyData> Files { get; private set; }
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'm working on adding a Windsor IoC container to an existing WinForms application that uses an MVP UI design pattern. I'm trying to determine a good approach to resgistering a datacontext that depends on a connection string supplied at runtime. The problem is that I cannot create a datacontext until the user selects a database, i.e. a 'connection string' after the application has loaded. Granted only one datacontext is generally used, but sometimes a user need to switch to a different database, i.e. creating a differnet datacontext. This leads to additional runtime dependencies as well.
public interface IProductsView
{
event EventHandler<ProductSelectedEventArgs> ProductSelectedEvent;
event EventHandler<StringEventArgs> ProductStatusEvent;
void ClearProductList();
void DisplayProductList(IList<Product> products);
Control Control { get; }
IProductsPresenter Presenter { get; set; }
}
public class ProductsPresenter : IProductsPresenter
{
public IProductsView View { get; set; }
private IProductRepository Repository { get; set; }
public ProductsPresenter(IProductsView view, IProductRepository repository)
{
View = view;
View.Presenter = this;
Repository = repository;
}
public void ProductSelected(IList<Product> products)
{
throw new NotImplementedException();
}
public void ShowProductList(string name)
{
IList<Product> productList;
if (string.IsNullOrEmpty(name))
productList = Repository.GetProducts();
else
productList = Repository.GetProductsByName(name);
View.DisplayProductList(productList);
}
}
public class ProductDao : IDisposable, IProductRepository
{
private MeasurementDataContext dataContext;
public ProductDao(MeasurementDataContext context)
{
dataContext = context;
}
public List<Product> GetProducts()
{
return dataContext.Products.Select(p => Mapper.Map(p)).ToList().OrderBy(x => x.Name).ToList();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
if (dataContext != null)
{
dataContext.Dispose();
dataContext = null;
}
}
~ProductDao()
{
this.Dispose(false);
}
}
So this means that the Presenter in my View is null until the IProductRepository is created, which in turn depends on creating a MeasurementDataContext. I have these component regisitered in a IWindsorInstaller like so:
container.Register(Component.For<IProductsView>()
.ImplementedBy<ViewProductsControl>());
container.Register(Component.For<IProductsPresenter>()
.ImplementedBy<ProductsPresenter>());
Do I need to use Named and DependsOn which supply a unique name and connectionString argument for each datacontext?
What I currently do to register the data context at runtime after the user has selected a database
kernel.Register(Component.For<MeasurementDataContext>()
.UsingFactoryMethod(() => new MeasurementDataContext(conn)));
and then `Resolve' my Views and set their Presenters. I know this is not good design, but it's a brute force way of resolving my dependcies.
Thanks
UPDATE:
I changed the way I registered my datacontext's in the installer to the following:
container.Register(Component.For<DataContext>().ImplementedBy<MeasurementDataContext>().Named("Localhost").DependsOn(new { connectionString = conn }));
and then modified my model's constructor to:
public ProductDao(DataContext context)
{
dataContext = context as MeasurementDataContext;
}
All components will resolve with the right key:
kernel.Resolve<DataContext>(cbo.SelectedItem.ToString());
What about injecting a wrapper class to hold the connection string and have the datacontext objects use that? Something along these lines:
public class ConnectionStringProvider : IConnectionStringProvider
{
private string _value;
public event EventHandler ConnectionStringChanged;
public string ConnectionString
{
get { return _value; }
set
{
_value = value;
var del = ValueChanged;
if (del != null)
del(this, EventArgs.Empty);
}
}
}
Register this with and singleton lifestyle. This way your application can set or update the connection string on a single object and everyone who depends on it will be notified of the change.
Hi I am trying to use telerik Busy indicator with MVVM. I have the Busy indicator in Mainwindow. When there is an action(button click) on one of the user controls that is in the window, the user controls view model sends an message to the MinwindowviewModel. On the message the is busy indicator should show up. But this is not working. Why is this not working?
User controls view model
public class GetCustomerVM : ViewModelBase
{
private int _CustomerId;
public int CustomerId
{
get { return _CustomerId; }
set
{
if (value != _CustomerId)
{
_CustomerId = value;
RaisePropertyChanged("CustomerId");
}
}
}
public RelayCommand StartFetching { get; private set; }
public GetCustomerVM()
{
StartFetching = new RelayCommand(OnStart);
}
private void OnStart()
{
Messenger.Default.Send(new Start());
AccountDetails a = AccountRepository.GetAccountDetailsByID(CustomerId);
Messenger.Default.Send(new Complete());
}
}
The User Control View model is:
private bool _IsBusy;
public bool IsBusy
{
get { return _IsBusy; }
set
{
if (value != _IsBusy)
{
_IsBusy = value;
RaisePropertyChanged("IsBusy");
}
}
}
public WRunEngineVM()
{
RegisterForMessages();
}
private void RegisterForMessages()
{
Messenger.Default.Register<Start>(this, OnStart);
Messenger.Default.Register<Complete>(this, OnComplete);
}
private void OnComplete(Complete obj)
{
IsBusy = false;
}
private void OnStart(Start obj)
{
IsBusy = true;
}
In the Main window View, the root element is
<telerik:RadBusyIndicator IsBusy="{Binding IsBusy}" telerik:StyleManager.Theme="Windows7">
What does AccountDetails a = AccountRepository.GetAccountDetailsByID(CustomerId); do? My guess is that whatever is happeneing there is running on the UI thread. Because it is all happeneing on the UI thread, there is never a chance of the UI to refresh and show the RadBusyIndicator. Try moving all of the work on in OnStart to a BackgroundWorker, including sending the messages. You will run into issues here, because the messages will be updating the UI thread from a background thread, so you will need to use the Dispatcher to set IsBusy to true or false.