How to communicate backend status messages to frontend? - c#

I have a WPF project and a Console one, the point of the WPF is to be the frontend UI and the console application is the logic that does the actual work.
In my backend I have a class with a method that does the work.
public static class BackendClass
{
public static void DoWork(ref string output)
{
//actual work
}
}
From the MVVM frontend my view model starts a task for this method and I want to be able to show status messages on the frontend about it. Things like "Started work.", "Doing so-and-so.", "Finished." and etc.
The code in my view model is:
class ViewModel : INotifyPropertyChanged
{
static string backendOutput;
public string BackendOutput
{
get => backendOutput;
set
{
if (backendOutput != value)
{
backendOutput = value;
OnPropertyChanged("BackendOutput");
}
}
}
public RelayCommand ExecuteCommand { get; private set; }
Task executionTask;
public event PropertyChangedEventHandler PropertyChanged;
public ViewModel()
{
executionTask = new Task(() => BackendClass.DoWork(ref BackendOutput));
}
void OnExecute()
{
executionTask.Start();
ExecuteCommand.RaiseCanExecuteChanged();
}
bool CanExecute()
{
return (executionTask.Status != TaskStatus.Running &&
executionTask.Status != TaskStatus.WaitingToRun);
}
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The "BackendOutput" property is data binded to a text block in the WPF window.
I was thinking of passing the "BackendOutput" property so the "DoWork" method can append its status messages to it, thus raising the changed event, updating the frontend.
However if I try to assign it outside of the constructor I get the error that a property can't be a field initializer or something like that and in this case I get "property can't be passed as a ref parameter".
So how should I alert the frontend of what status messages the back is pumping?

ViewModel communicates with View via PropertyChanged event. So Model also can have an event. ViewModel subscribes to that event, updates property with event data, View gets updated.
Events are kind of protected delegates. So as a first step try to introduce a delegate:
public static void DoWork(Action<string> notifier)
{
notifier("output value");
}
and
executionTask = new Task(() => BackendClass.DoWork(str => { BackendOutput = str; }));

Related

WPF InteractionRequest is always null

Hi I'm new to WPF and XAML, I'm trying to utilize MVVMCross's MvxInteraction to interact with the user to get a "YES" or "NO" confirmation based off this example.
I've been hitting a snag on getting the interaction to subscribe to an event handler as the interaction is always null. I can see that from the references that the interaction variable see each other based on the binding, so I'm not sure what's going on. I've looked around and found this, that states for me to bring my binding later into my UserControl View behind code, so I used a dispatcher, but that did not work either.
VIEW MODEL
public class StudentDetailsViewModel : MvxViewModel
{
private InteractionRequest<YesNoQuestion> _interaction = new InteractionRequest<YesNoQuestion>();
public IInteractionRequest Interaction => _interaction;
}
VIEW.XAML.CS
public partial class StudentDetailsView : MvxWpfView
{
private InteractionRequest<YesNoQuestion> _interaction;
public StudentDetailsView()
{
InitializeComponent();
Dispatcher.BeginInvoke(new Action(() => BindInteractions()), DispatcherPriority.ContextIdle, null);
}
public InteractionRequest<YesNoQuestion> Interaction
{
get => _interaction;
set
{
if(_interaction != null)
{
_interaction.Requested -= OnInteractionRequested;
}
_interaction = value;
_interaction.Requested += OnInteractionRequested; //***RUN TIME NULL EXCEPTION***
}
}
private void OnInteractionRequested(object sender, InteractionRequestedEventArgs eventArgs)
{
var yesNoQuestion = eventArgs.Callback;
}
private void BindInteractions()
{
var set = this.CreateBindingSet<StudentDetailsView, StudentDetailsViewModel>();
set.Bind(this).For(view => view.Interaction).To(viewModel => viewModel.Interaction).OneWay();
set.Apply();
}
}
INTERACTION CLASS
public class YesNoQuestion
{
public bool? Confirmation { get; set; }
public string Question { get; set; }
public YesNoQuestion(string message)
{
Question = message;
}
}
My second question is that I'm a little confused on what they implemented with the "ShowDialog" and "DialogStatus" here within their example:
private async void OnInteractionRequested(object sender, MvxValueEventArgs<YesNoQuestion> eventArgs)
{
var yesNoQuestion = eventArgs.Value;
// show dialog
var status = await ShowDialog(yesNoQuestion.Question);
yesNoQuestion.YesNoCallback(status == DialogStatus.Yes);
}
Are they simply calling upon another usercontrol view to show itself through a ShowDialog Method?
_interaction.Requested += OnInteractionRequested; //***RUN TIME NULL EXCEPTION***
Somehow this is always null on the first startup, and then it will assign the proper interaction later, so add a null check to solve this. Maybe we need to confirm with MVVMCross itself.
Second, you can handle whatever you want to display on interaction request, for example, shows MessageBox with yes no button type or pop another view to display custom message box one. Since this runs on the WPF layer.

Pass parameter from viewmodels to another

When I searched,I found,how to bind values from viewmodel to view but not viewmodel to viewmodel.can anyone help me to do that. what i need is to pass Authentication to other viewmodel.I am new in the MVVM world so please give me more detail.
my ViewModel look like this
public class ModelView_Authentication : INotifyPropertyChanged
{
//Binding authentication
private Authentication _authentication;
public Authentication authentication
{
get { return _authentication; }
set
{
_authentication = value;
NotifayPropertyChanged("_authentication");
}
}
//Command Button
public ModelView_Authentication()
{
authentication = new Authentication();
ButtonCommand = new ViewModdelCommand(exeMethode, canexeMethode);
}
public ICommand ButtonCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private bool canexeMethode(Object param)
{
return true;
}
//run this Command Onclick Button
private void exeMethode(Object param)
{
}
protected void NotifayPropertyChanged(string s)
{
PropertyChangedEventHandler pc = PropertyChanged;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(s));
}
}
//Run Assync Login
public static async Task<string> main(Authentication authentication)
{
var tocken = await Login.GetConnection(authentication);
return tocken.ToString();
}
}
need is to pass Authentication to other viewmodel
Your main ViewModel adheres to INotifyPropertyChanged, you can have your other VMs subscribe to the notification process of the main VM and acquire changes to specific properties as needed.
Just have a reference to the main VM, it is as easy as that. Where the VMs get their references, that process is up to you.
A good place is on App class. Since the App class is known throughout each of the namespaces, setup up a static property on it, set it after the main VM is created, and then access the it as needed.
public static ModelView_Authentication AuthVM { get; set; }
the access such as
var mainVM = App.AuthVM;

WPF UserControl: Invoke usercontrol's public method from viewModel

I have a MVVM WPF application in C#, NET 3.5 and Visual Studio 2008.
From the app main xaml I import a user control.
This user control has some public methods, there are two I am interested in.
One method to start an animation and another to stop it.
From my view's constructor in code-behind (xaml.cs), I call the user control public method to start the animation to show it to user while I am loading some data into my gridview within listview. The method to load the data is called form my view model.
So now, when the loading task is finished, I need to call the another user control public method to stop animation but I do not know how to do this from my view model.
Any ideas? I cannot touch the user control as this is not mine.
Below some piece of code.
XAML:
xmlns:controlProgress="clr-namespace:Common.XAML.Controls.Progress;assembly=Common.XAML"
<controlProgress:Progress x:Name="Progress"
Grid.ZIndex="3"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="150"
CustomText="Loading...">
Code-behind (xaml.cs):
public MyView(ViewModelSession vm)
: base(vm)
{
InitializeComponent();
Progress.StartAnimation();
}
View Model:
public MyViewModel(Session session)
: base(session)
{
this.LoadDataIntoGridView();
}
You can use the INotifyPropertyChanged Interface e.g. create an ViewModelBase
public class ViewModelBase
: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then you use this for your ViewModel and add a Property IsLoading
public class MyViewModel : ViewModelBase
{
private bool _isLoading;
public bool IsLoading
{
get { return _isLoading; }
set
{
if(_isLoading == value) return;
_isLoading = value;
OnPropertyChanged();
}
}
Then in your View Codebehind use the PropertyChanged event of the ViewModel to Start/Stop Animation.
Then you can set the bool in your ViewModel to start stop closing animation
in your view
UPDATE
public class MyView
{
private readonly MyViewModel _viewModel;
public MyView(MyViewModel viewModel)
: base(viewModel)
{
InitializeComponent();
_viewModel = viewModel;
_viewModel.PropertyChanged +=OnPropertyChanged;
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(MyViewModel.IsLoading))
{
if (_viewModel.IsLoading)
{
Progress.StartAnimation();
}
else
{
Progress.StopAnimation();
}
}
}
}
You could put a boolean property in your view model to track if the loading has been completed, after that the property will be set to true.
public class MyViewModel
{
public bool IsLoadComplete { get; set; }
public MyViewModel()
{
this.LoadDataIntoGridView();
}
}
Then in your codebehind you can start a Task to track changes in that property of the DataContext:
public MyView(MyViewModel vm)
{
InitializeComponent();
Progress.StartAnimation();
Task.Run(() =>
{
var dataContext = DataContext as MyViewModel;
while (true)
{
if (dataContext.IsLoadComplete)
break;
Task.Delay(100);
}
Dispatcher.BeginInvoke(new Action(() => { Progress.StopAnimation(); }));
});
}
You have to use Dispatcher.BeginInvoke to queue the call in the UI thread. Of course this is not a ready-to-production solution. You may provide Datacontext until View has been constructed in which case you must refactor, also you may keep track of the task you have just started and may be support cancellation with a CancellationToken. This is only a sample

DataBinding and UpdateSourceTrigger on Content

Okay, so I've been attempting to learn a bit of MVVM. It's been only three days and have come to a dead end. The application simply retrieves the client's external ip address and updates the new ip address in a label found in my UI. However, rather than just going the old-fashioned way of simply doing,
ipAdd.Content = getNewIp();
and ending the story end there. Rather, I decided to use some MVVM to achieve the following:
Retrieve ip address, then display it to the user in a label
Query the database and update the user's ip address
Rinse, repeat every five minutes.
The reason I chose to go more MVVM is to use UpdateSourceTrigger and UpdateCommand. Essentially, when the ip address gets updated, I want to do something with it (as mentioned above). I've seen in many tutorials where UpdateCommand is used on buttons and UpdateSourceTrigger used on input boxes, but nothing that shows how to do it with labels, yet it seems that it should be tied in to the same concept; it might not be explicity a button that is being clicked, but some back code that executes and performs the same task as a button. For starters, here's what I have so far:
Model
namespace IPdevices
{
class IP : INotifyPropertyChanged
{
private string _add;
public string add
{
get
{
return _add;
}
set
{
_add = value;
OnPropertyChange("add");
}
}
private void OnPropertyChange(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
ViewModel
namespace IPdevices
{
class IPViewModel
{
private IP _ip;
public IPViewModel()
{
UpdateCommand = new IPUpdateCommand(this);
}
public ICommand UpdateCommand
{
get; private set;
}
public bool CanUpdate
{
get
{
return true; // just say yes for now
}
}
public IP IP
{
get
{
return _ip;
}
set
{
_ip = value;
}
}
public void updateClientIp()
{
Console.WriteLine("_updating client ip and query database");
}
}
}
ICommand
namespace IPdevices
{
class IPUpdateCommand : ICommand
{
private IPViewModel _viewModel;
public IPUpdateCommand(IPViewModel vm)
{
_viewModel = vm;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public bool CanExecute(object parameter)
{
return _viewModel.CanUpdate;
}
public void Execute(object parameter)
{
_viewModel.updateClientIp();
}
}
}
MainWindow
public Main()
{
InitializeComponent();
ipAdd.DataContext = new IPViewModel();
}
View
<Label x:Name="ipAdd" Content="{Binding Path=IP.add}"/>
What I'm trying to achieve here can easily be done without going MVVM (perhaps overkill as well) and simply calling a few helper classes to update the user's ip on the db side, but for the sake of getting used to doing things MVVM, I tried incorporating the way I did, though with no luck.
Is there any way to do this, or even better, should it be done this way?

Return value of a Property from child ViewModel to parent ViewModel

In my WPF MVVM app, using Caliburn.Micro, I have a ViewModel, CreateServiceViewModel that, on a button click, opens a GridView in a seperate window for the User to chose a Row from.
I created another ViewModel for this, MemberSearchViewModel which has two properties:
private Member selectedMember;
public Member SelectedMember
{
get { return selectedMember; }
set { selectedMember = value; }
}
private IList<Member> members;
public IList<Member> Members
{
get { return members; }
set { members = value; }
}
How do I get that SelectedMember value back to the calling ViewModel? That ViewModel has a property of Service.SelectedMember.
EventAggregator is what you could use... One of many solutions I am sure.
public class MessageNotifier{
public object Content{get;set;}
public string Message {get;set;}
}
//MEF bits here
public class HelloWorldViewModel: Screen, IHandle<MessageNotifier>{
private readonly IEventAggregator _eventAggregator
//MEF constructor bits
public YourViewModel(IEventAggregator eventAggregator){
_eventAggregator = eventAggregator;
}
public override OnActivate(){
_eventAggregator.Subscribe(this);
}
public override OnDeactivate(){
_eventAggregator.UnSubscribe(this);
}
//I Handle all messages with this signature and if the message applies to me do something
//
public void Handle(MesssageNotifier _notifier){
if(_notifier.Message == "NewSelectedItem"){
//do something with the content of the selectedItem
var x = _notifier.Content
}
}
}
//MEF attrs
public class HelloWorld2ViewModel: Screen{
private readonly IEventAggregator _eventAggregator
//MEF attrs
public HelloWorld2ViewModel(IEventAggregator eventAggregator){
_eventAggregator = eventAggregator;
}
public someobject SelectedItem{
get{ return _someobject ;}
set{ _someobject = value;
NotifyOfPropertyChange(()=>SelectedItem);
_eventAggregator.Publish(new MessageNotifier(){ Content = SelectedItem, Message="NewSelectedItem"});
}
}
One option is to utilize NotifyPropertyChanged. Since you are working with ViewModels, they most likely implement INotifyPropertyChanged, which you can make use of just as the framework does.
When your CreateServiceViewModel creates the MemberSearchViewModel, it would just subscribe to the PropertyChanged event:
//This goes wherever you create your child view model
var memberSearchViewModel = new MemberSearchViewModel(); //Or using a service locator, if applicable
memberSearchViewModel.PropertyChanged += OnMemberSearchPropertyChanged;
private void OnMemberSearchPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "SelectedMember")
{
//Code to respond to a change in the Member
}
}
And then in your MemberSearchViewModel, you simply raise the NotifyPropertyChanged event when the user has selected a member from the grid.
EDIT:
As #DNH correctly notes in the comments, using event handlers like this can lead to memory leaks if not properly cleaned up. So when you are finished with the MemberSearchViewModel, make sure to unsubscribe to the PropertyChanged event. So for example, if you only need it until the user selects a member, you could put it inside the Property Changed Handler itself (I've switched it to use a class-level variable to hold the ViewModel):
private void OnMemberSearchPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == "SelectedMember")
{
//Code to respond to a change in the Member
//Unsubscribe so the view model can be garbage collected
_memberSearchViewModel.PropertyChanged -= OnMemberSearchPropertyChanged;
_memberSearchViewModel = null;
}
}
One option would be to store MemberSearchViewModel as a field of CreateServiceViewModel and define CreateServiceViewModel.SelectedMember property as follows:
public Member SelectedMember
{
get
{
return _memberSearchViewModel.SelectedMember;
}
set
{
_memberSearchViewModel.SelectedMember = value;
}
}
How about?
public interface INotifyMe<T>
{
T ResultToNotify { get; set; }
}
public class CreateServiceViewModel : ViewModelBase, INotifyMe<Member>
{
// implement the interface as you like...
}
public class MemberSearchViewModel : ViewModelBase
{
public MemberSearchViewModel(INotifyMe<Member> toBeNotified)
{
// initialize field and so on...
}
}
Now you could let listen CreateServiceViewModel to its own property and you won't have to think about the removal of the event listener.
Well of course to do the more classical way you could alternatively use an interface like this.
public interface INotifyMe<T>
{
void Notify(T result);
}
As a follow-up to my comment, here's an example using Prism - I've never used Caliburn.
Create an event - the event's payload will be your SelectedMember:
public class YourEvent:CompositePresentationEvent<YourEventPayload>{}
Publish the event:
EventAggregator.GetEvent<YourEvent>().Publish(YourEventPayload);
Subscribe to the event:
EventAggregator.GetEvent<YourEvent>().Subscribe((i) => ...);

Categories