I have .NET Core app with WPF. I am trying to convert it to use DI and MVVM pattern.
I am stuck now. At the application startup I subscribe for some events. Event handler process it and change title of all opened windows.
Something like:
protected void OnSomething(object sender, EventArgs args)
{
App.Current.Dispatcher.BeginInvoke(new Action(() =>
{
foreach (Window window in App.Current.Windows)
{
if (window.Title.EndsWith("something"))
{
window.Title = window.Title.Substring(0, window.Title.Length - "something".Length);
}
}
}));
}
protected void OnSomethingElse(object sender, EventArgs args)
{
App.Current.Dispatcher.BeginInvoke(new Action(() =>
{
foreach (Window window in App.Current.Windows)
{
if (!window.Title.EndsWith("something"))
{
window.Title = window.Title + "something";
}
}
}));
}
I would like to move window title construction into ViewModel of given window. Lets say:
public string WindowTitle => "My title" + (currentState ? "something" : string.Empty);
What is the best approach to update title of all opened windows? I believe that I can not change title in the way I am doing it right now. And I feel current way is not the right/best way.
This is my approach.
Create a resource like this:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:system="clr-namespace:System;assembly=mscorlib">
[...]
<system:String x:Key="AppTitle">Default App Title</system:String>
[...]
</ResourceDictionary>
And set that resource as your window title:
<Window
[...]
Title="{DynamicResource AppTitle}">
[...]
</Window>
Note: it should be a DynamicResource.
Then create a utility method like this:
public class Utils {
public static void ChangeAppTitle(string title) {
Application.Current.Resources["AppTitle"] = title;
}
}
Now we can access to this method in entire application.
And finally you can use it like this:
public class SampleViewModel {
private void SampleMethod() {
Utils.ChangeAppTitle("New app title");
}
}
For this scenario I would use my self-written EventService. This EventService looks like:
public class EventService<T>
{
private static EventService<T> instance;
private static readonly object instanceLock = new object();
private readonly Dictionary<string, List<Action<T>> > events;
private EventServiceExt()
{
events = new Dictionary<string, List<Action<T>>>();
}
public static EventService<T> Instance
{
get
{
lock (instanceLock)
{
if (instance == null)
{
instance = new EventService<T>();
}
}
return instance;
}
}
public void Subscribe(string eventName, Action<T> action)
{
lock (instanceLock)
{
if (events.ContainsKey(eventName))
events[eventName].Add(action);
else
events.Add(eventName, new List<Action<T>>{ action });
}
}
public void Raise(string eventName, T parameter)
{
lock (instanceLock)
{
if (events != null && (events.ContainsKey(eventName) && events[eventName] != null))
{
var actions = events[eventName];
if (actions != null)
{
foreach (var action in actions.Where(action => action != null))
{
action(parameter);
}
}
}
}
}
/// <summary>
///
/// </summary>
/// <param name="eventName"></param>
/// <param name="action"></param>
public void Unsubscribe(string eventName, Action<T> action)
{
lock (instanceLock)
{
if (events.ContainsKey(eventName) && events[eventName] != null && action != null)
{
List<Action<T>> actions = events[eventName];
actions.Remove(action);
if (actions.Count == 0)
events.Remove(eventName);
}
}
}
}
With this you can subscribe to an event in each window like:
EventService<string>.Instance.Subscribe("windowtitlechanged", OnWindowTitleChanged);
And if you want to change the window-title you just have to raise the event with:
EventService<string>.Instance.Raise("windowtitlechanged", "My new window-title");
Related
So I'm making a slot machine in C#. I'm really new to C# and I am really bad at it.
Up to this point my project has been going fine. But now I want to randomize the images shown, when the 'spin' Button is clicked.
I've tried a lot of different things. The solutions I have found are either with the use of a PictureBox or nothing close to what I'm working on.
If someone could take a look at my code and push me in the right direction, I would be really grateful.
Thanks in advance!
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
namespace Prb.Slot.Machine.Wpf
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
int CoinInsert = 0;
private static Random random;
public enum SlotMachineIcon
{
Banana,
BigWin,
Cherry,
Lemon,
Orange,
Plum,
Seven,
Strawberry,
Watermelon
}
public MainWindow()
{
InitializeComponent();
}
private static void Init()
{
if (random == null) random = new Random();
}
public static int Random(int min, int max)
{
Init();
return random.Next(min, max);
}
void UpdateImage(Image wpfImage, SlotMachineIcon newIcon)
{
DirectoryInfo directoryInfo = new DirectoryInfo(Environment.CurrentDirectory);
directoryInfo = new DirectoryInfo(directoryInfo.Parent.Parent.Parent.Parent.FullName);
Uri uri = new Uri($"{directoryInfo.FullName}/images/{newIcon}.png");
wpfImage.Source = new BitmapImage(uri);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
lblCoinsInserted.Content = 0;
lblCoinBalance.Content = 0;
lblCoinsWon.Content = 0;
UpdateImage(imgLeft, SlotMachineIcon.Cherry);
UpdateImage(imgMiddle, SlotMachineIcon.Banana);
UpdateImage(imgRight, SlotMachineIcon.Seven);
}
private void btnInsertCoins_Click(object sender, RoutedEventArgs e)
{
int.TryParse(txtInsertCoins.Text, out int InsertCoins);
if (InsertCoins > 0)
{
CoinInsert += int.Parse(txtInsertCoins.Text.ToString());
lblCoinBalance.Content = (int)lblCoinBalance.Content + Convert.ToInt32(txtInsertCoins.Text);
lblCoinsInserted.Content = CoinInsert;
txtInsertCoins.Clear();
}
else
{
MessageBox.Show("Gelieve strikt positieve getallen in te vullen", "Ongeldig aantal munten", MessageBoxButton.OK, MessageBoxImage.Warning);
txtInsertCoins.Clear();
}
}
private void btnSpin_Click(object sender, RoutedEventArgs e)
{
int InsertedCoins = Convert.ToInt32(lblCoinsInserted.Content);
int CoinsBalance = Convert.ToInt32(lblCoinBalance.Content);
/*var v = Enum.GetValues(typeof(SlotMachineIcon));
int number = random.Next(10);*/
if (InsertedCoins == 0 | CoinsBalance == 0)
{
MessageBox.Show("Gelieve eerst munten in te werpen", "Geen munten ingeworpen", MessageBoxButton.OK, MessageBoxImage.Warning);
}
else
{
lblCoinBalance.Content = CoinsBalance - 1;
UpdateImage(imgLeft, SlotMachineIcon.Strawberry);
UpdateImage(imgMiddle, SlotMachineIcon.Watermelon);
UpdateImage(imgRight, SlotMachineIcon.Watermelon);
}
}
}
}
Edit: moved out random declaration as #EmondErno pointed it out.
This method returns a random icon every time you call it:
private Random random = new();
private SlotMachineIcon GetRandomIcon()
{
return (SlotMachineIcon)random.Next(10); //don't forget to update this number if you add or remove icons
}
Then call it in every UpdateImage method like:
UpdateImage(imgLeft, GetRandomIcon());
UpdateImage(imgMiddle, GetRandomIcon());
UpdateImage(imgRight, GetRandomIcon());
You're trying to do everything in the code behind, which is a terrible mistake for many reasons, among which your program will get hard to maintain read and update at some point and you are tight coupling the view and the logic of your program. You want to follow the MVVM pattern and put only in the code behind only the logic of the view (no data).
Also in your code, you're reinventing the updating system that already exists in WPF, you want to use the databinding and WPF updating system and get rid of all "update icon" logic in your program.
This is a ViewModel that you could use (.net 5.0):
public class SlotViewModel: ISlotViewModel, INotifyPropertyChanged
{
private Random _r = new();
private int _slotChoicesCount;
private SlotSet _currentSlotSet;
private ICommand _spinCommand;
public SlotViewModel()
{
_slotChoicesCount = Enum.GetNames(typeof(SlotMachineIcon)).Length;
}
private SlotSet GetNewSet() => new(Enumerable.Range(0,3).Select(o => (SlotMachineIcon)_r.Next(_slotChoicesCount)).ToList());
public SlotSet CurrentSlotSet
{
get => _currentSlotSet;
set
{
if (Equals(value, _currentSlotSet)) return;
_currentSlotSet = value;
OnPropertyChanged();
}
}
public ICommand SpinCommand => _spinCommand ??= new DelegateCommand(s => { CurrentSlotSet = GetNewSet(); }, s => true);
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The most important part is that your ViewModel implements INotifyPropertyChanged. When you uses SpinCommand, it updates the property CurrentSlotSet, and that's all you need to worry about. All the rest is taken care of by the WPF databinding system.
SlotSet is a convenient way to present an immutable result:
public class SlotSet
{
public SlotMachineIcon Left { get; }
public SlotMachineIcon Middle { get; }
public SlotMachineIcon Right { get; }
public SlotSet(IList<SlotMachineIcon> triad)
{
Left = triad[0];
Middle = triad[1];
Right = triad[2];
}
public bool IsWinner => Left == Middle && Middle == Right; // just an example
}
ISlotViewModel is the interface (contract) that your ViewModel satisfies.
public interface ISlotViewModel
{
ICommand SpinCommand { get; }
SlotSet CurrentSlotSet { get; set; }
}
The helper class DelegateCommand:
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<object> execute, Predicate<object> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
In your View, XAML part, your only need something as simple as this:
<Button Command="{Binding SpinCommand}">spin</Button>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding CurrentSlotSet.Left}"/>
<Image Source="{Binding CurrentSlotSet.Middle}"/>
<Image Source="{Binding CurrentSlotSet.Right}"/>
</StackPanel>
And in the Windows markup has this:
xmlns:local="clr-namespace:SlotMachine"
d:DataContext="{d:DesignInstance Type=local:SlotViewModel, IsDesignTimeCreatable=True}"
The code behind is as simple as this:
public ISlotViewModel ViewModel
{
get { return (ISlotViewModel)DataContext; }
set { DataContext = value; }
}
public SlotView() // or MainWindow
{
InitializeComponent();
ViewModel = new SlotViewModel();
}
The only thing missing here is to add a converter in each of your <Image, which will convert a SlotMachineIcon value into the image path.
PS: if you don't have resharper, you may need this class too:
[AttributeUsage(AttributeTargets.Method)]
public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute
{
public NotifyPropertyChangedInvocatorAttribute() { }
public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName)
{
ParameterName = parameterName;
}
[CanBeNull] public string ParameterName { get; }
}
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.
So I have this object:
public class SomeObject: INotifyPropertyChanged
{
public decimal AlertLevel {
get {
return alertLevel;
}
set {
if(alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
private void OnPropertyChanged(string propertyName) {
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Suppose I am changing this object on a thread that is NOT the GUI thread. How can I have this object raise the PropertyChanged event on the same thread as the GUI when I don't have a reference to any GUI component in this class?
Normally the event subscriber should be responsible for marshalling the calls to the UI thread if necessary.
But if the class in question is UI specific (a.k.a view model), as soon it is created on the UI thread, you can capture the SynchronizationContext and use it for raising the event like this:
public class SomeObject : INotifyPropertyChanged
{
private SynchronizationContext syncContext;
public SomeObject()
{
syncContext = SynchronizationContext.Current;
}
private decimal alertLevel;
public decimal AlertLevel
{
get { return alertLevel; }
set
{
if (alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
if (syncContext != null)
syncContext.Post(_ => handler(this, new PropertyChangedEventArgs(propertyName)), null);
else
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Alternatively you can pass SynchronizationContext via constructor.
Yet another way is to keep the object intact, but data bind to it via intermediate synchronized binding source as described here Update elements in BindingSource via separate task.
for WPF - Add the following references:
PresentationFramework.dll
WindowsBase.dll
In your background thread - wrap the code that needs access to UI into a dispatcher.Invoke()
using System.Windows;
using System.Windows.Threading;
...
//this is needed because Application.Current will be NULL for a WinForms application, since this is a WPF construct so you need this ugly hack
if (System.Windows.Application.Current == null)
new System.Windows.Application();
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
//Do Your magic here
}), DispatcherPriority.Render);
for WinForms use
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, new Action(() => {
//Do Your magic here
}));
An even better idea, without using any WPF references:
public class GUIThreadDispatcher {
private static volatile GUIThreadDispatcher itsSingleton;
private WeakReference itsDispatcher;
private GUIThreadDispatcher() { }
public static GUIThreadDispatcher Instance
{
get
{
if (itsSingleton == null)
itsSingleton = new GUIThreadDispatcher();
return itsSingleton;
}
}
public void Init(Control ctrl) {
itsDispatcher = new WeakReference(ctrl);
}
public void Invoke(Action method) {
ExecuteAction((Control ctrl) => DoInGuiThread(ctrl, method, forceBeginInvoke: false));
}
public void BeginInvoke(Action method) {
ExecuteAction((Control ctrl) => DoInGuiThread(ctrl, method, forceBeginInvoke: true));
}
private void ExecuteAction(Action<Control> action) {
if (itsDispatcher.IsAlive) {
var ctrl = itsDispatcher.Target as Control;
if (ctrl != null) {
action(ctrl);
}
}
}
public static void DoInGuiThread(Control ctrl, Action action, bool forceBeginInvoke = false) {
if (ctrl.InvokeRequired) {
if (forceBeginInvoke)
ctrl.BeginInvoke(action);
else
ctrl.Invoke(action);
}
else {
action();
}
}
}
}
And initialize like this:
private void MainForm_Load(object sender, EventArgs e) {
//setup the ability to use the GUI Thread when needed via a static reference
GUIThreadDispatcher.Instance.Init(this);
...
}
And use like this:
public class SomeObject: INotifyPropertyChanged
{
public decimal AlertLevel {
get {
return alertLevel;
}
set {
if(alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
private void OnPropertyChanged(string propertyName) {
GUIThreadDispatcher.Instance.BeginInvoke(() => {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
});
}}
Found an even better answer without having to use a WeakReference to the form control and NO WPF References based on https://lostechies.com/gabrielschenker/2009/01/23/synchronizing-calls-to-the-ui-in-a-multi-threaded-application/ and Ivan's answer above:
public class GUIThreadDispatcher {
private static volatile GUIThreadDispatcher itsSingleton;
private SynchronizationContext itsSyncContext;
private GUIThreadDispatcher() {}
/// <summary>
/// This needs to be called on the GUI Thread somewhere
/// </summary>
public void Init() {
itsSyncContext = AsyncOperationManager.SynchronizationContext;
}
public static GUIThreadDispatcher Instance
{
get
{
if (itsSingleton == null)
itsSingleton = new GUIThreadDispatcher();
return itsSingleton;
}
}
public void Invoke(Action method) {
itsSyncContext.Send((state) => { method(); }, null);
}
public void BeginInvoke(Action method) {
itsSyncContext.Post((state) => { method(); }, null);
}
}
}
And initialize like this:
private void MainForm_Load(object sender, EventArgs e) {
//setup the ability to use the GUI Thread when needed via a static reference
GUIThreadDispatcher.Instance.Init();
...
}
And use like this:
public class SomeObject: INotifyPropertyChanged
{
public decimal AlertLevel {
get {
return alertLevel;
}
set {
if(alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
private void OnPropertyChanged(string propertyName) {
GUIThreadDispatcher.Instance.BeginInvoke(() => {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
});
}}
This turned out to be a clean implementation (relatively). Just had to include a reference to WindowsBase.dll which turns out to be a WPF library so eh..., not extremely pleased with it but it's a solution...:
public class GUIThreadDispatcher {
private static volatile GUIThreadDispatcher itsSingleton;
private Dispatcher itsDispatcher;
private GUIThreadDispatcher() { }
public static GUIThreadDispatcher Instance
{
get
{
if (itsSingleton == null)
itsSingleton = new GUIThreadDispatcher();
return itsSingleton;
}
}
public void Init() {
itsDispatcher = Dispatcher.CurrentDispatcher;
}
public object Invoke(Action method, DispatcherPriority priority = DispatcherPriority.Render, params object[] args) {
return itsDispatcher.Invoke(method, priority, args);
}
public DispatcherOperation BeginInvoke(Action method, DispatcherPriority priority = DispatcherPriority.Render, params object[] args) {
return itsDispatcher.BeginInvoke(method, priority, args);
}
Then initialize it like this:
static class Program {
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main() {
GUIThreadDispatcher.Instance.Init(); //setup the ability to use the GUI Thread when needed via a static reference
Application.Run(new MainForm());
}
}
And then use it like this:
public class SomeObject: INotifyPropertyChanged
{
public decimal AlertLevel {
get {
return alertLevel;
}
set {
if(alertLevel == value) return;
alertLevel = value;
OnPropertyChanged("AlertLevel");
}
private void OnPropertyChanged(string propertyName) {
GUIThreadDispatcher.Instance.BeginInvoke(() => {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
});
}}
I have mainviewmodel where it makes a collection of itemviewmodel and binds that data to a longlist in Mainpage.xaml. Now in the process of making a collection of ItemViewModel I am making a web request and when that downloads I would make a list.
I wanted to know in MainPage as when this download finishes.
MainViewModel
public void LoadData()
{
if (this.CanLoad)
{
WebClient dealsOfDay = new WebClient();
dealsOfDay.DownloadStringCompleted += dealsOfDay_DownloadStringCompleted;
dealsOfDay.DownloadStringAsync(new Uri("http://loadsomedata.php"));
}
else
{
this.IsDataLoaded = false;
}
}
void dealsOfDay_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null && e.Result != null)
{
var deals=//something making a collection.
Items = new ObservableCollection<ItemViewModel>(deals);
NotifyPropertyChanged("Items");
this.IsDataLoaded = true;
}
else
{
MessageBox.Show("Error");
}
}
App.xaml.cs
private static MainViewModel viewModel = null;
/// <summary>
/// A static ViewModel used by the views to bind against.
/// </summary>
/// <returns>The MainViewModel object.</returns>
public static MainViewModel ViewModel
{
get
{
// Delay creation of the view model until necessary
if (viewModel == null)
viewModel = new MainViewModel();
return viewModel;
}
}
MainPage.xaml.cs
Inside the constructor I would set this.
DataContext = App.ViewModel;
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
I think you have no need for bool IsDataLoaded. Instead that create event in MainViewModel and register in Main page.
public event EventHandler DataLoadedEvent;
void dealsOfDay_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error == null && e.Result != null)
{
var deals=//something making a collection.
Items = new ObservableCollection<ItemViewModel>(deals);
NotifyPropertyChanged("Items");
if ( DataLoadedEvent != null)
{
DataLoadedEvent(this, new EventHandler());
}
}
else
{
MessageBox.Show("Error");
}
}
Now in MainPage constructor register this event.
App.ViewModel.DataLoadedEvent += new EventHandler(data_loadedEvent);
void data_loadedEvent(object sender, EventArgs e)
{
App.ViewModel.LoadData();
}
Remember also that you can subscribe more methods to DownloadStringCompleted - and they will be fired, so maybe there is no need to create new event. Also in many cases you can just perform actions in dealsOfDay_DownloadStringCompleted.
But if you want to make an Event which will be fired when DownloadCompletes it can look like this:
Create a delegate:
public delegate void StatusUpdateHandler(object sender, StatusEventArgs e);
public event StatusUpdateHandler OnUpdateStatus;
For this purpose you need somewhere to define StatusEventArgs Class:
public class StatusEventArgs : EventArgs
{
public string Status { get; private set; }
public StatusEventArgs(string status)
{
Status = status;
}
}
Then your method can look like this:
private void UpdateStatus(string status)
{
if (OnUpdateStatus == null) return;
StatusEventArgs args = new StatusEventArgs(status);
OnUpdateStatus(this, args);
}
Then you can freely subscribe to that event and put in your dealsOfDay_DownloadStringCompleted:
UpdateStatus("Downloaded");
I have a button on my Main Window. When I click it I want another Window to popup on top of the Main Window.
Main Window is still visible and should be the parent of this new window.
I been looking around and not sure how to do it, some people suggested to use the Messenger to do this but did not really give an example.
Hi I didnt have MVVMLight so I have used custom Messanger because to make picture clearer how things work.
MessageType
public enum MessageType
{
DataLoaded,
OpenWindow,
SetFocus,
OpenExceptionWindow,
Refresh
//etc
}
Message
public class Message
{
public Message(MessageType messageType, object message)
{
MessageType = messageType;
MessageObject = message;
}
public MessageType MessageType { get; private set; }
public object MessageObject { get; private set; }
}
Messanger
public class Messanger
{
//Singleton
private Messanger()
{ }
static Messanger instance;
public static Messanger Instance
{
get{return instance ?? (instance=new Messanger());}
}
static Dictionary<string, Action<Message>> dictionary = new Dictionary<string, Action<Message>>();
//View Calls this and register the delegate corresponding to the unique token
public void Register(string token,Action<Message> action)
{
if (dictionary.ContainsKey(token))
throw new Exception("Already registered");
if (action == null)
throw new ArgumentNullException("action is null");
dictionary.Add(token, action);
}
public void UnRegister(string token)
{
if(dictionary.ContainsKey(token))
dictionary.Remove(token);
}
//ViewModel Calls this and pass the token and Message.
//the registered delegate is looked up in dictionary corresponding to that token and
//Corresponding register delegate fired.
public void SendMessage(string token,Message message)
{
if (dictionary.ContainsKey(token))
dictionary[token](message);
}
}
ViewBase
public class ViewBase:Window
{
protected string Token { get; private set; }
public ViewBase()
{
Token = Guid.NewGuid().ToString();
//Register to Messanger
Messanger.Instance.Register(Token, HandleMessages);
//UnRegister On Closing or Closed
this.Closing +=(s,e)=> Messanger.Instance.UnRegister(Token);
}
//Handle Common Messages to all Windows Here
void HandleMessages(Message message)
{
switch (message.MessageType)
{
case MessageType.OpenExceptionWindow:
Exception ex = message.MessageObject as Exception;
ExceptionWindow window = new ExceptionWindow();
window.Exception = ex;
window.ShowDialog();
break;
//other common cases should be handled here
default : HandleWindowLevelMessage(message);
break;
}
}
protected virtual void HandleWindowLevelMessage(Message message)
{
}
}
View
public partial class Mywindow : ViewBase
{
public Mywindow()
{
InitializeComponent();
DataContext = new MyViewModel(Token);
}
protected override void HandleWindowLevelMessage(Message message)
{
//open window according to OP Requirement
if (message.MessageType == MessageType.OpenWindow)
{
string windowName = message.ToString();
if (windowName != null)
{
//logic to get the window . I assume that OP have some logic to get the child window this is just temporary
var window = Application.Current.Windows.OfType<Window>().FirstOrDefault(s=>s.Name==windowName);
if (window != null)
{
window.Owner=this;
window.Show();
}
}
}
base.HandleWindowLevelMessage(message);
}
}
View.xaml Here first element is not Window now
<local:ViewBase x:Class="WpfApplication4.Mywindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication4"
Title="Mywindow" Height="300" Width="300">
<Grid>
<Button Content="ok" Click="Button_Click_1"/>
</Grid>
ViewModelBase
public class ViewModelBase : INotifyPropertyChanged
{
public ViewModelBase(string token)
{
Token = token;
}
protected string Token { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
void RaisePropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
ViewModel
public class MyViewModel : ViewModelBase
{
public MyViewModel(string token)
: base(token)
{
}
//say OP want to open window on Command Execute
public void OnCommand()
{
Messanger.Instance.SendMessage(Token, new Message(MessageType.OpenWindow, "MyChildWindow"));
}
}
I hope this will help. This is simple code to understand feel free to ask.