I can't seem to get a method in my ViewModel to run successfully from my XAML code behind when using NotifyIcon. The method executes correctly, as tested with debugging mode using breakpoints, but nothing happens in the View.
The method in question is RefreshData, and it can be called from either a button in the View (works as expected), or from right clicking the NotifyIcon and selecting Refresh Data (does nothing). I'll post relevant code below. Any help would be appreciated!
MainWindow constructor in CodeBehind
public MainWindow()
{
try
{
MM = new MMViewModel();
InitializeComponent();
DataContext = MM;
_notifyIcon = new NotifyIcon();
_notifyIcon.DoubleClick += (s, args) => ShowMainWindow(this);
_notifyIcon.Icon = Migration_Monitor_v2.Properties.Resources.mmc;
_notifyIcon.Visible = true;
Closing += MainWindow_Closing;
CreateContextMenu();
}
catch (Exception e)
{
logger.Error("App failed with exception: ", e);
}
}
private void CreateContextMenu()
{
_notifyIcon.ContextMenuStrip = new ContextMenuStrip();
_notifyIcon.ContextMenuStrip.Items.Add("Refresh Data").Click += (s,e) => MM.RefreshData();
_notifyIcon.ContextMenuStrip.Items.Add("Exit").Click += (s, e) => ExitApplication(this);
}
RefreshData method in ViewModel (works when executed from the Refresh button in the View)
public void RefreshData()
{
InfoPanelVisible = Visibility.Hidden;
InfoSummaryVisible = Visibility.Visible;
Task.Run(() => LoadData());
n = DateTime.Now;
ProgressBarText = "Click a project to show progress";
ProgressBarValue = 0;
lastRefresh.Reset();
lastRefresh.Start();
}
LoadData method (and associated methods) called from RefreshData
public async void LoadData()
{
IsLoading = Visibility.Visible;
await GetWebApiInfo();
MonitorData downloadInfo = main;
try { AssignDataToControls(downloadInfo); }
catch (Exception e) { Console.WriteLine("Error: " + e); }
finally { IsLoading = Visibility.Hidden; }
}
public void AssignDataToControls(MonitorData mon)
{
MainPanel.Clear();
MonitorText.Clear();
mon.MainPanel.ToList().ForEach(x => MainPanel.Add(x));
mon.MonitorText.ToList().ForEach(x => MonitorText.Add(x));
Information = mon.Information;
ProgressData = mon.progList;
}
public async Task GetWebApiInfo()
{
var url = "::::WEB API ADDRESS::::";
string responseFromServer;
using (HttpClient _client = new HttpClient())
using (var dataStream = await _client.GetStreamAsync(url))
using (var reader = new StreamReader(dataStream, Encoding.Unicode))
responseFromServer = await reader.ReadToEndAsync();
var deserializer = new JavaScriptSerializer();
main = deserializer.Deserialize<MonitorData>(responseFromServer);
}
RefreshCommand from ViewModel Commands.cs file
internal class RefreshCommand : ICommand
{
public RefreshCommand(MMViewModel viewModel)
{
_viewModel = viewModel;
}
private MMViewModel _viewModel;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _viewModel.CanRefresh;
}
public void Execute(object parameter)
{
_viewModel.RefreshData();
}
}
Solved this issue. The problem was that my ViewModel was being constructed three separate times, so it was unclear to the command which instance was being referenced.
To fix this, I removed references to the ViewModel in my XAML Window definition space. There were 2 references I thought I needed for the ViewModel as a local namespace (i.e. x:local.VM="MM.MyViewModel"). After removing those, the ViewModel is only being constructed once and all code is running as intended. Thanks to #Will for his help getting to the bottom of this!
Related
How to put an ActivityIndicator on Xamarin Forms OnStart() function.
I am check Network access on OnStart() function.
Bind the ActivityIndicator to a property in your BaseViewModel (IsBusy).
View
<ActivityIndicator Color="Accent" IsVisible="{Binding IsBusy}" IsRunning="{Binding IsBusy}" />
BaseViewModel (Inherited by all ViewModels)
private bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set
{
_isBusy = value;
OnPropertyChanged("IsBusy");
}
}
Get yourself a good MVVM framework (Prism) and put the network check in the OnNavigatedTo method for your start page.
public override void OnNavigatedTo(INavigationParameters parameters)
{
IsBusy = true;
await CheckNetwork();
IsBusy = false;
}
Now you can paste that same ActivityIndicator snippet into any page (XAML) that is bound to a ViewModel inheriting BaseViewModel and it will just work when you set IsBusy.
Haven't used ActivityIndicator, but this nuget works great: Acr.UserDialogs.
After installing and adding the initialization part in the MainActivity or ios equivalent, just add the following code between resource intensive threads in either your code-behind file or viewmodel (mvvm):
This works for code-behind file:
protected override async void OnAppearing(object sender, EventArgs e)
{
base.ViewIsAppearing(sender, e);
UserDialogs.Instance.ShowLoading();
//do stuff here
UserDialogs.Instance.HideLoading();
}
This works for FreshMVVM framework:
protected override async void ViewIsAppearing(object sender, EventArgs e)
{
base.ViewIsAppearing(sender, e);
UserDialogs.Instance.ShowLoading();
//do stuff here
UserDialogs.Instance.HideLoading();
}
I'm using network checking in my projects too, please check this:
using Plugin.Connectivity;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace PetBellies.View
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class NoConnection : ContentPage
{
private bool wasNotConn = false;
public NoConnection()
{
InitializeComponent();
CrossConnectivity.Current.ConnectivityChanged += async (sender, args) =>
{
if (CrossConnectivity.Current.IsConnected && !wasNotConn)
{
wasNotConn = true;
await Navigation.PushModalAsync(new NavigationPage(new MainPage()));
}
else
{
wasNotConn = false;
}
};
}
public NoConnection(bool isFromLogin)
{
CrossConnectivity.Current.ConnectivityChanged += async (sender, args) =>
{
if (CrossConnectivity.Current.IsConnected && !wasNotConn)
{
wasNotConn = true;
var page = new LoginPage();
var navPage = new NavigationPage(page);
NavigationPage.SetHasNavigationBar(navPage, false);
await Navigation.PushModalAsync(navPage);
}
else
{
wasNotConn = false;
}
};
}
}
}
https://github.com/officialdoniald/PetBellies/blob/master/PetBellies/PetBellies/View/NoConnection.xaml.cs
If the connection lost, the application navigate to this page and stay on this page while the connection is unavailable.
i'm currently facing an issue in C# WPF. I wrote an application, that generates long running reports in a background task. I am using prism with MVVM and trying to run the expensive background task with a Async ICommand implementation and a BackgroundWorker. But when i try to retrieve the resulting report
Report = asyncTask.Result;
i get an InvalidOperationException stating "The calling thread cannot access this object because a different thread owns it.".
Yes, i have already tried to invoke a dispatcher (its the first thing you'll find on google, stackoverflow etc when you search for the exception message). I have tried several variants like for instance:
Dispatcher.CurrentDispatcher.Invoke(() => Report = asyncTaks.Result);
or
Report.Dispatcher.Invoke(() => Report = asyncTask.Result);
but each time i get this exception.
I am suspecting that the way i am calling the report UI is not adequate.
The structure looks in brief as follows:
MainWindowViewModel
-> SubWindowCommand
SubWindowViewModel
-> GenerateReportCommand
ReportViewModel
-> GenerateReportAsyncCommand
<- Exception on callback
I am out of ideas, does anybody have a clue what i might be doing wrong?
Below are a few code fragments
Report Generator View Model:
public class ReportFlowDocumentViewModel : BindableBase
{
private IUnityContainer _container;
private bool _isReportGenerationInProgress;
private FlowDocument _report;
public FlowDocument Report
{
get { return _report; }
set
{
if (object.Equals(_report, value) == false)
{
SetProperty(ref _report, value);
}
}
}
public bool IsReportGenerationInProgress
{
get { return _isReportGenerationInProgress; }
set
{
if (_isReportGenerationInProgress != value)
{
SetProperty(ref _isReportGenerationInProgress, value);
}
}
}
public ReportFlowDocumentView View { get; set; }
public DelegateCommand PrintCommand { get; set; }
public AsyncCommand GenerateReportCommand { get; set; }
public ReportFlowDocumentViewModel(ReportFlowDocumentView view, IUnityContainer c)
{
_container = c;
view.DataContext = this;
View = view;
view.ViewModel = this;
InitializeGenerateReportAsyncCommand();
IsReportGenerationInProgress = false;
}
private void InitializeGenerateReportAsyncCommand()
{
GenerateReportCommand = new CreateReportAsyncCommand(_container);
GenerateReportCommand.RunWorkerStarting += (sender, args) =>
{
IsReportGenerationInProgress = true;
var reportGeneratorService = new ReportGeneratorService();
_container.RegisterInstance<ReportGeneratorService>(reportGeneratorService);
};
GenerateReportCommand.RunWorkerCompleted += (sender, args) =>
{
IsReportGenerationInProgress = false;
var report = GenerateReportCommand.Result as FlowDocument;
var dispatcher = Application.Current.MainWindow.Dispatcher;
try
{
dispatcher.VerifyAccess();
if (Report == null)
{
Report = new FlowDocument();
}
Dispatcher.CurrentDispatcher.Invoke(() =>
{
Report = report;
});
}
catch (InvalidOperationException inex)
{
// here goes my exception
}
};
}
public void TriggerReportGeneration()
{
GenerateReportCommand.Execute(null);
}
}
This is how i start the ReportView Window
var reportViewModel = _container.Resolve<ReportFlowDocumentViewModel>();
View.ReportViewerWindowAction.WindowContent = reportViewModel.View;
reportViewModel.TriggerReportGeneration();
var popupNotification = new Notification()
{
Title = "Report Viewer",
};
ShowReportViewerRequest.Raise(popupNotification);
with
ShowReportViewerRequest = new InteractionRequest<INotification>();
AsyncCommand definition
public abstract class AsyncCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public event EventHandler RunWorkerStarting;
public event RunWorkerCompletedEventHandler RunWorkerCompleted;
public abstract object Result { get; protected set; }
private bool _isExecuting;
public bool IsExecuting
{
get { return _isExecuting; }
private set
{
_isExecuting = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
protected abstract void OnExecute(object parameter);
public void Execute(object parameter)
{
try
{
onRunWorkerStarting();
var worker = new BackgroundWorker();
worker.DoWork += ((sender, e) => OnExecute(e.Argument));
worker.RunWorkerCompleted += ((sender, e) => onRunWorkerCompleted(e));
worker.RunWorkerAsync(parameter);
}
catch (Exception ex)
{
onRunWorkerCompleted(new RunWorkerCompletedEventArgs(null, ex, true));
}
}
private void onRunWorkerStarting()
{
IsExecuting = true;
if (RunWorkerStarting != null)
RunWorkerStarting(this, EventArgs.Empty);
}
private void onRunWorkerCompleted(RunWorkerCompletedEventArgs e)
{
IsExecuting = false;
if (RunWorkerCompleted != null)
RunWorkerCompleted(this, e);
}
public virtual bool CanExecute(object parameter)
{
return !IsExecuting;
}
}
CreateReportAsyncCommand:
public class CreateReportAsyncCommand : AsyncCommand
{
private IUnityContainer _container;
public CreateReportAsyncCommand(IUnityContainer container)
{
_container = container;
}
public override object Result { get; protected set; }
protected override void OnExecute(object parameter)
{
var reportGeneratorService = _container.Resolve<ReportGeneratorService>();
Result = reportGeneratorService?.GenerateReport();
}
}
I think i understand my problem now. I cannot use FlowDocument in a BackgroundThread and update it afterwards, right?
So how can i create a FlowDocument within a background thread, or at least generate the document asynchronously?
The FlowDocument i am creating contains a lot of tables and when i run the report generation synchronously, the UI freezes for about 30seconds, which is unacceptable for regular use.
EDIT:
Found the Solution here:
Creating FlowDocument on BackgroundWorker thread
In brief: I create a flow document within my ReportGeneratorService and then i serialize the FlowDocument to string. In my background worker callback i receive the serialized string and deserialize it - both with XamlWriter and XmlReader as shown here
Your Problem is that you create FlowDocument in another thread. Put your data to the non GUI container and use them after bg comes back in UI thread.
there is a propery on mainwindow of my app that is updated by a function taht runs in background (DoWork). BackgroundWorker is implemented in ViewModel. If I open an new page and comme back on the mainwindow this property takes automatically its default value with which it was initialized in the ViewModel constructor.
What should I do to keep this property updated even if a new window is opened?
public class ImageViewModel : INotifyPropertyChanged
{
private string currentData;
public ImageViewModel()
{
img = new ImageFile { path = "" };
currentData = "There is currently no update";
this.worker = new BackgroundWorker();
this.worker.DoWork += this.DoWork;
this.worker.ProgressChanged += this.ProgressChanged;
this.worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_Completed);
this.worker.WorkerReportsProgress = true;
}
public string CurrentData
{
get { return this.currentData; }
private set
{
if (this.currentData != value)
{
this.currentData = value;
this.RaisePropertyChanged("CurrentData");
}
}
}
...
private void DoWork(object sender, DoWorkEventArgs e)
{
...
this.CurrentData = "file X is being updated...";
...
}
void worker_Completed(object sender, RunWorkerCompletedEventArgs e)
{
this.CurrentData = "There is currently no update...";
}
You can create a Singleton for your ViewModel like this:
Add this to your ViewModel class:
public static YourViewModelType Instance { get; set; }
In your Window.xaml.cs then assign the DataContext like this:
if(YourViewModel.Instance == null)
{
YourViewModel.Instance = new YourViewModelType();
}
this.DataContext = YourViewModel.Instance;
Note:
This will cause all your Windows to have the same DataContext. You should only do this if every Window needs the same Properties (Bindings) and stuff.
Otherwise I strongly recommend using different ViewModels per window.
I have an issue with my custom event not updating a text box on my UWP application. If I replace the Textbox1.Text with debug.Writeline it works. Is there a reason why I can't update a textbox using an event? If I use the Progress object it works. I am just trying to figure out why it wouldnt work with my own custom event. Thank you
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void button_click(object sender, RoutedEventArgs e)
{
recData myRecDataobject = new recData();
myRecDataobject.dataRecvEvent += () =>
{
textBox2.Text = "Event Occured"; // This throws an error
Debug.WriteLine("test2");
};
Progress<int> progress = new Progress<int>();
myRecDataobject.getDataMethodAsync(progress);
progress.ProgressChanged += (o, result) =>
{
textBox1.Text = result.ToString();
};
}
}
public class recData
{
public delegate void myEvenetHandlerDelegate();
public event myEvenetHandlerDelegate dataRecvEvent;
private int _myValue;
public int myValue
{
get
{
return _myValue;
}
set
{
_myValue = value;
}
}
public async void getDataMethodAsync(Progress<int> progress)
{
await getDataMethod(progress);
}
private Task getDataMethod(IProgress<int> progress)
{
return Task.Factory.StartNew(() =>
{
for (int i = 0; i < 1000; i++)
{
Task.Delay(2000).Wait();
if (dataRecvEvent != null)
{
dataRecvEvent();
progress.Report(i);
}
}
});
}
}
You are trying to update a XAML property from a background thread. This doesn't work (your error should be "access denied").
Use Dispatcher.BeginInvoke to schedule the TextBox property update on the UI thread.
I'm making a WPF application where I use WebClient to download file. I want to show the ProgressPercentage within a ProgressBar controller. I have a method in a class where I use WebClient to download file. My question is how do I Databind the ProgressBar to the e.ProgressPercentage.
Method in a Class (DownloadFile):
public async Task DownloadProtocol(string address, string location)
{
Uri Uri = new Uri(address);
using (WebClient client = new WebClient())
{
//client.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
//client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgress);
client.DownloadProgressChanged += (o, e) =>
{
Console.WriteLine(e.BytesReceived + " " + e.ProgressPercentage);
//ProgressBar = e.ProgressPercentage???
};
client.DownloadFileCompleted += (o, e) =>
{
if (e.Cancelled == true)
{
Console.WriteLine("Download has been canceled.");
}
else
{
Console.WriteLine("Download completed!");
}
};
await client.DownloadFileTaskAsync(Uri, location);
}
}
ProgressBar:
<ProgressBar HorizontalAlignment="Left" Height="24" Margin="130,127,0,0" VerticalAlignment="Top" Width="249"/>
For a clean solution you need a ViewModel class and in it we create a StartDownload method and you can call it by a Command or under a button click in your window.
On the other hand there is a good Type named IProgress<T>. It works as a informer for us and you can play with it like the following sample ;)
Inside DownloadViewModel.cs:
public sealed class DownloadViewModel : INotifyPropertyChanged
{
private readonly IProgress<double> _progress;
private double _progressValue;
public double ProgressValue
{
get
{
return _progressValue;
}
set
{
_progressValue = value;
OnPropertyChanged();
}
}
public DownloadViewModel()
{
_progress = new Progress<double>(ProgressValueChanged);
}
private void ProgressValueChanged(double d)
{
ProgressValue = d;
}
public async void StartDownload(string address, string location)
{
await new MyDlClass().DownloadProtocol(_progress, address, location);
}
//-----------------------------------------------------------------------
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Inside MyDlClass. cs
public class MyDlClass
{
public async Task DownloadProtocol(IProgress<double> progress, string address, string location)
{
Uri Uri = new Uri(address);
using (WebClient client = new WebClient())
{
//client.DownloadFileCompleted += new AsyncCompletedEventHandler(Completed);
//client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgress);
client.DownloadProgressChanged += (o, e) =>
{
Console.WriteLine(e.BytesReceived + " " + e.ProgressPercentage);
progress.Report(e.ProgressPercentage);
};
client.DownloadFileCompleted += (o, e) =>
{
if (e.Cancelled == true)
{
Console.WriteLine("Download has been canceled.");
}
else
{
Console.WriteLine("Download completed!");
}
};
await client.DownloadFileTaskAsync(Uri, location);
}
}
}
Inside MyWindow.Xaml.cs & MyWindow.Xaml:
Now you should fill your window DataContext with an instance of DownloadViewModel class (by Xaml or Code-Behind).
Binding to ProgressValue property of DownloadViewModel.cs class:
<ProgressBar HorizontalAlignment="Left" Height="24" Margin="130,127,0,0" VerticalAlignment="Top" Width="249"
Minimum="0"
Maximum="100"
Value="{Binding Path=ProgressValue}"/>
Finnaly, write in your button OnClick:
if(this.DataContext!=null)
((DownloadViewModel)this.DataContext).StartDownload("__#Address__","__#Location__");
else
MessageBox.Show("DataContext is Null!");
Result:
You should add an IProgress<double> progress parameter to your method, whenever your client wants to update progress then call progress.Report(). IProgress on your view/window side will point to a method where you'll implement the MyProgressBar.Value = value or whatever else.
Using IProgress effectively removes the interaction with the target control that will provide updates, in addition it also takes care of calling the dispatcher so you don't get usual invalid cross-thread call errors.
See this post for an example:
http://blogs.msdn.com/b/dotnet/archive/2012/06/06/async-in-4-5-enabling-progress-and-cancellation-in-async-apis.aspx