I have a WPF User control and here is the code, where I initialize the view model and subscribe for the event.
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
this.DataContext = new MyUserControlViewModel();
((MyUserControlViewModel)this.DataContext).MainModel.MessageDataNew.CollectionChanged += NewMessage_CollectionChanged;
}
This is the collection change event and it's not firing :(
private void NewMessage_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (MessageStatus != null)
{
var border = (Border)VisualTreeHelper.GetChild(MessageStatus, 0);
var scrollViewer = (ScrollViewer)VisualTreeHelper.GetChild(border, 0);
scrollViewer.ScrollToBottom();
}
}
This is my view model constructor. I am using GalaSoft.MvvmLight.Messaging
public class MyUserControlViewModel: INotifyPropertyChanged
{
public MyUserControlViewModel()
{
Messenger.Default.Register<string>(this, "SimulatorLogs", AddtoCollection);
}
public MainModel MainModel
{
get { return _mainModel; }
set
{ _mainModel = value;
RaisePropertyChanged(() => MainModel);
}
}
private void AddtoCollection(string measurementData)
{
MainModel.MessageDataNew.Add(measurementData);
}
}
Related
I have two windows. In the first window I would like to start the second window with some preference values (e. g. "MaxWords"). The second window holds a class with an interface for INotifyPropertyChanged. This works as expected...
public partial class PreviewPreferences : Window
{
public PreviewPreferences()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
public class ViewModel
{
public Preferences preferences { get; private set; }
public ViewModel()
{
preferences = new Preferences();
}
}
public class Preferences : INotifyPropertyChanged
{
private int _maxWords = 10;
/// <summary>
/// Default constructor
/// </summary>
public Preferences() { }
/// <summary>
/// Max words
/// </summary>
public int MaxWords
{
get { return this._maxWords; }
set { this._maxWords = value; this.OnPropertyChanged("MaxWords"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
The second window should updating the first window and adds some user controls to the grid. The question is not how to add the controls... it is more how to raise the event from the preference class that the value MaxWords is changed?
private void button_preview_preferences_Click(object sender, RoutedEventArgs e)
{
PreviewPreferences previewPreferences = new PreviewPreferences();
previewPreferences.Show();
Preferences preferences = new Preferences();
preferences.PropertyChanged += HandleChangedPreferences;
}
private void HandleChangedPreferences(object sender, PropertyChangedEventArgs e)
{
// this will never be raised
for (int i = 0; i < MaxWords; i++)
{
...
}
}
you have two instance of Preferences in button_preview_preferences_Click method. The first and important one (the one that changes) is hidden in PreviewPreferences DataContext:
private void button_preview_preferences_Click(object sender, RoutedEventArgs e)
{
var previewPreferences = new PreviewPreferences();
var preferences = (previewPreferences.DataContext as ViewModel).preferences;
preferences.PropertyChanged += HandleChangedPreferences;
previewPreferences.Show();
}
I suggest to invert the logic - create preferences outside ViewModel, and create ViewModel outside PreviewPreferences view:
public partial class PreviewPreferences : Window
{
public PreviewPreferences()
{
InitializeComponent();
}
}
public class ViewModel
{
public Preferences preferences { get; private set; }
public ViewModel(Preferences p)
{
preferences = p;
}
}
private void button_preview_preferences_Click(object sender, RoutedEventArgs e)
{
var preferences = new Preferences();
preferences.PropertyChanged += HandleChangedPreferences;
var previewPreferences = new PreviewPreferences();
previewPreferences.DataContext = new ViewModel(preferences);
previewPreferences.Show();
}
I'm trying to learn the MVVM structure. How can I update a variable that changes constantly in another class in the UI.
I created a simple example because the project codes are too much. But I failed.
I would be very grateful if you could tell me where I went wrong. Thanks.
MyModel
public class Temperature : INotifyPropertyChanged
{
private double _memsTemperature;
private double _cpuTemperature;
private double _animalTemperature;
public double MemsTemperature
{
get { return _memsTemperature; }
set
{
_memsTemperature = value;
OnPropertyChanged("MemsTemperature");
}
}
public double CpuTemperature
{
get { return _cpuTemperature; }
set
{
_cpuTemperature = value;
OnPropertyChanged("CpuTemperature");
}
}
public double AnimalTemperature
{
get { return _animalTemperature; }
set
{
_animalTemperature = value;
OnPropertyChanged("AnimalTemperature");
}
}
System.Windows.Threading.DispatcherTimer dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
public Temperature()
{
dispatcherTimer.Tick += DispatcherTimer_Tick;
dispatcherTimer.Interval = TimeSpan.FromSeconds(1);
dispatcherTimer.Start();
}
private void DispatcherTimer_Tick(object sender, System.EventArgs e)
{
MemsTemperature = MemsTemperature + 1;
CpuTemperature = CpuTemperature + 2;
AnimalTemperature = AnimalTemperature + 3;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
MainWindowViewModel
public class MainWindowViewModel
{
public double MemTemp { get; set; }
public MainWindowViewModel()
{
MemTemp = new Temperature().MemsTemperature;
}
}
Main Window Xaml and C# Code
<TextBlock Text="{Binding MemTemp, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
The MainWindowViewModel should expose a Temperature property, e.g. like this:
public class MainWindowViewModel
{
public Temperature Temperature { get; } = new Temperature();
}
and the Binding should then look like this:
<TextBlock Text="{Binding Temperature.MemsTemperature}"/>
Neither Mode=TwoWay nor UpdateSourceTrigger=PropertyChanged makes sense on the Binding of a TextBlock's Text property.
The OnPropertyChanged method would simpler and safer be implemented like this:
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
You have a XAML page with UI controls that bind to those constantly-changing properties. When you send out the PropertyChanged notifications, the UI control will automatically update itself.
The problem with the code you wrote is that you never bound to the actual temperature. XAML doesn't know how to translate MemTemp into anything other than it's name unless you write a DataTemplate for it.
For example, (assuming a grid) something like this:
<TextBlock Grid.Row="0" Grid.Column="0" Text="Animal: "/>
<TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding MemTemp.AnimalTemperature}"/>
I would define an explicit worker class which performs the measurements. This class
has an event (OnMeasurement), which can be subscribed in the ViewModel:
// Arguments for the mesurement event (temperature, ...)
public class MeasurementEventArgs : EventArgs
{
public double Temperature { get; }
public MeasurementEventArgs(double temperature)
{
Temperature = temperature;
}
}
public class MeasurementWorker
{
private readonly CancellationTokenSource _tcs = new CancellationTokenSource();
// Provides an event we can subscribe in the view model.
public event Action<object, MeasurementEventArgs> OnMeasurement;
public void Stop()
{
_tcs.Cancel();
}
// Measurement routine. Perform a measurement every second.
public async Task Start()
{
try
{
var rnd = new Random();
while (!_tcs.IsCancellationRequested)
{
var temperature = 20 * rnd.NextDouble();
OnMeasurement?.Invoke(this, new MeasurementEventArgs(temperature));
await Task.Delay(1000, _tcs.Token);
}
}
catch (TaskCanceledException) { }
// TODO: Create an error event to catch exceptions from here.
catch { }
}
}
In your MainWindow class you instantiate your viewmodel and your worker:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel(new MeasurementWorker());
}
// Register in XAML with <Window ... Closing="StopMeasurement">
public async void StopMeasurement(object sender, System.ComponentModel.CancelEventArgs e)
{
var vm = DataContext as MainWindowViewModel;
await vm.StopMeasurement();
}
}
In your view model you can subscribe to the worker event and raise OnPropertyChanged in your callback function:
public class MainWindowViewModel : INotifyPropertyChanged
{
private double _memsTemperature;
private readonly MeasurementWorker _mw;
private readonly Task _measurementWorkerTask;
public double MemsTemperature
{
get => _memsTemperature;
set
{
_memsTemperature = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(MemsTemperature)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void ProcessMeasurement(object sender, MeasurementEventArgs args)
{
MemsTemperature = args.Temperature;
}
// You can call this if you want to stop your measurement. Should be called if you close your app.
public async Task StopMeasurement()
{
_mw.OnMeasurement -= ProcessMeasurement;
_mw.Stop();
// Clean shutdown
await _measurementWorkerTask;
}
public MainWindowViewModel(MeasurementWorker mw)
{
_mw = mw;
_mw.OnMeasurement += ProcessMeasurement;
_measurementWorkerTask = _mw.Start();
}
}
I'm new to WPF. I'm creating a POS desktop application by using WPF MVVM pattern as front-end development. (I have try my best to make this question as short as possible.)
Scenario: I have a MainViewModel which will show AuthView (and AuthViewModel) by default whenever user open the application. After user fill in the form and click the Login button in AuthView, LoginCommand will be called on the view, if login successful, they will be redirect to DashboardView.
MainWindow.xaml
<Grid>
<ContentControl Content="{Binding SelectedViewModel}"/>
</Grid>
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
if (SelectedViewModel == null)
{
SelectedViewModel = new AuthViewModel();
}
else
{
SelectedViewModel = new DashboardViewModel();
}
}
private ViewModelBase _selectedViewModel;
public ViewModelBase SelectedViewModel
{
get { return _selectedViewModel; }
set { _selectedViewModel = value; OnPropertyChanged(nameof(SelectedViewModel)); }
}
public void ChangeToDashboard()
{
SelectedViewModel = new DashboardViewModel();
}
}
AuthViewModel.cs
public class AuthViewModel : ViewModelBase
{
public AuthViewModel()
{
loginCommand = new RelayCommand(Login);
}
#region Login
private RelayCommand loginCommand;
public RelayCommand LoginCommand
{
get { return loginCommand; }
}
private async void Login()
{
try
{
Response = await callLoginAPI; //some custom login occurs here
if (Response.Status == "ok")
{
//change viewModel to DashboardViewModel screen
MainViewModel MainViewModel = new MainViewModel();
MainViewModel.ChangeToDashboard();
}
}
catch (Exception e)
{
//
}
}
#endregion
}
Problem: I have go through a lot of SA solution but still unable to switch the view after user login successfully.
Question: How can I trigger the MainViewModel to change UI after I have change the SelectedViewModel property (after user login successfully, response.status == ok)? or is there any other better (as simple as possible) way to achieve what I am trying to do?
AuthViewModel can generate event about login
public class AuthViewModel : ViewModelBase
{
public AuthViewModel()
{
loginCommand = new RelayCommand(Login);
}
public event EventHandler LoginCompleted;
protected virtual void OnLoginCompleted(EventArgs e)
{
EventHandler handler = LoginCompleted;
handler?.Invoke(this, e);
}
private RelayCommand loginCommand;
public RelayCommand LoginCommand
{
get { return loginCommand; }
}
private async void Login()
{
try
{
Response = await callLoginAPI(); //some custom login occurs here
if (Response.Status == "ok")
{
OnLoginCompleted(EventArgs.Empty);
}
}
catch (Exception e)
{
//
}
}
}
and MainViewModel can handle that event:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
if (SelectedViewModel == null)
{
var vm = new AuthViewModel();
vm.LoginCompleted += (sender, e) => ChangeToDashboard();
SelectedViewModel = vm;
}
else
{
SelectedViewModel = new DashboardViewModel();
}
}
private ViewModelBase _selectedViewModel;
public ViewModelBase SelectedViewModel
{
get { return _selectedViewModel; }
set { _selectedViewModel = value; OnPropertyChanged(nameof(SelectedViewModel)); }
}
private void ChangeToDashboard()
{
SelectedViewModel = new DashboardViewModel();
}
}
I've added a DialogService in order to open a ProductView, so far the ShowDetailDialog() is working as expected.
Issue:
I call Close() on the ProductView, the view isn't closed. I debugged this issue by setting a break point on the call to the dialog service close method.
When I stepped through the code, the null check shows that productView is null, which prevents Close() from being called.
Does anyone have idea why productView is null? (although it's showing data on the view)
DialogService:(hosts the Show and Close methods)
namespace MongoDBApp.Services
{
class DialogService : IDialogService
{
Window productView = null;
ProductView _productView;
public DialogService()
{
_productView = new ProductView();
}
public void CloseDetailDialog()
{
if (productView != null)
productView.Close();
}
public void ShowDetailDialog()
{
_productView.ShowDialog();
}
}
}
ProductViewModel: (summary of ProductVM, calls the close method on SaveCommand)
private void SaveProduct(object product)
{
_dialogService.CloseDetailDialog();
Messenger.Default.Send<ProductModel>(SelectedProduct);
}
CustomerOrdersViewmodel: (Where the ShowDetailDialog() is called initially)
private void EditOrder(object obj)
{
Messenger.Default.Send<ProductModel>(SelectedProduct);
_dialogService.ShowDetailDialog();
}
This is how I have always closed my windows.
Here would be my command:
class CancelCommand : ICommand
{
private NewTruckViewModel newTruck;
public CancelCommand(NewTruckViewModel vm)
{
newTruck = vm;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
newTruck.Cancel();
}
}
Here is my view Model and the method that gets called from my command:
private NewTruck myWnd; //View Declaration
//Ctor where I set myView (myWnd) equal to a view that is passed in.
public NewTruckViewModel(ObservableCollection<Truck> Trucks, NewTruck wnd, bool inEditTruck)
{
myEngine.stopHeartBeatTimer();
editTruck = inEditTruck;
myWnd = wnd;
SaveTruckCommand = new SaveTruckCommand(this);
CancelCommand = new CancelCommand(this);
ClearCommand = new ClearCommand(this);
SetLevel1MTCommand = new SetLevel1MTCommand(this);
SetLevel2MTCommand = new SetLevel2MTCommand(this);
SetLevel3MTCommand = new SetLevel3MTCommand(this);
SetLevel1FLCommand = new SetLevel1FLCommand(this);
SetLevel2FLCommand = new SetLevel2FLCommand(this);
SetLevel3FLCommand = new SetLevel3FLCommand(this);
myTrucks = Trucks;
}
public void Cancel()
{
myWnd.Close();
}
This works for me.
I resolved the issue by implementing an IDialogService on the View. Then calling the Show() and Close() methods from the ViewModel.
Solution:
Interface:
public interface IDialogService
{
void CloseDialog();
void ShowDialog(EditProductViewModel prodVM);
}
View:
public partial class ProductView : Window, IDialogService
{
public ProductView()
{
InitializeComponent();
this.DataContext = new EditProductViewModel(this);
}
public void CloseDialog()
{
if (this != null)
this.Visibility = Visibility.Collapsed;
}
public void ShowDialog(EditProductViewModel prodVM)
{
this.DataContext = prodVM;
this.Show();
}
private void Window_Closed(object sender, EventArgs e)
{
this.Visibility = Visibility.Collapsed;
}
}
ViewModel #1:
private IDialogService _dialogService;
public CustomerOrdersViewModel(IDialogService dialogservice)
{
this._dialogService = dialogservice;
}
private void EditOrder(object obj)
{
EditProductViewModel pvm = new EditProductViewModel(_dialogService);
pvm.Present(pvm);
Messenger.Default.Send<ProductModel>(SelectedProduct);
}
ViewModel #2:
private IDialogService _dialogService;
public EditProductViewModel(IDialogService dialogService)
{
this._dialogService = dialogService;
}
private void SaveProduct(object product)
{
SelectedProduct = SelectedProductTemp;
_dialogService.CloseDialog();
}
public void Present(EditProductViewModel prodVM)
{
_dialogService.ShowDialog(prodVM);
}
I don't understand why my rectangles are not being shown.
I made the xaml, and data binded the canvas, and init properly.
What am I missing such that it only shows a blank screen.
It should show a digital figure 8.
MODEL:
namespace Final
{
class Model : INotifyPropertyChanged
{
// define our property chage event handler, part of data binding
public event PropertyChangedEventHandler PropertyChanged;
// implements method for data binding to any and all properties
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private double _topTopHorizontal;
public double topTopHorizontal
{
get { return _topTopHorizontal; }
set
{
_topTopHorizontal = value;
OnPropertyChanged("topTopHorizontal");
}
}
private double _leftTopHorizontal;
public double leftTopHorizontal
{
get { return _leftTopHorizontal; }
set
{
_leftTopHorizontal = value;
OnPropertyChanged("leftTopHorizontal");
}
}
public void initModel()
{
topTopHorizontal = 50;
leftTopHorizontal = 50;
}
}
}
Main
public partial class MainWindow : Window
{
private Model model;
public MainWindow()
{
InitializeComponent();
}
private void WindowLoaded(object sender, RoutedEventArgs e)
{
// create an instance of our Model
model = new Model();
model.initModel();
}
}
}
You haven't set the DataContext for this window.
In constructor add:
public MainWindow()
{
InitializeComponent();
model = new Model();
DataContext = model;
}
Therefor, your window can access "leftTopHorizontal" and "topTopHorizontal".
And in your xaml change:
Canvas.Top ="{Binding topTopHorizontal}"
Canvas.Left="{Binding leftTopHorizontal}"
with:
Canvas.Top ="{Binding model.topTopHorizontal}"
Canvas.Left="{Binding model.leftTopHorizontal}"