WPF share string between 2 viewmodels - c#

I'm a newbie in this WPF world, I have the following situation:
I am developing a multimedia-related application using VLC and the Caliburn.Micro, I have encountered a problem where I need the variable TotalTime from the MainViewModel to be shared with the TextBox on the SettingsViewModel.
This variable happens to change every second, so it has to be notified every second.
MainViewModel -> string TotalTime
SettingsViewModel -> TextBox Time
I have tried to do it with events, but I haven't succeeded.

If you nest the SettingsViewModel in the MainViewModel as a property (say Settings), you can bind an UI element to it like this:
Text = "{Binding Path=Settings.TotalTime}"
if the View's DataContext is set to an instance of the MainViewModel

In general, the solution to this problem is a singleton messenger that both view-models have injected into them on creation (usually from an IoC container). In your scenario, SettingsViewModel would subscribe to messages of a particular type, say TotalTimeChangedMessage, and MainViewModel would send messages of that type whenever TotalTime changed. The message simply contains the current value of TotalTime, e.g.:
public sealed class TotalTimeChangedMessage
{
public string totalTime;
public TotalTimeChangedMessage(string totalTime)
{
this.totalTime = totalTime;
}
public string TotalTime
{
get { return this.totalTime; }
}
}
Caliburn.Micro contains an event aggregator that you can use to pass messages in this fashion between view-models. Your MainViewModel would send the message like this:
public class MainViewModel
{
private readonly IEventAggregator _eventAggregator;
public MainViewModel(IEventAggregator eventAggregator)
{
_eventAggregator = eventAggregator;
}
public string TotalTime
{
set
{
this.totalTime = value;
_eventAggregator.Publish(new TotalTimeChangedMessage(this.totalTime));
}
}
}
..and your SettingsViewModel would handle it like this:
public class SettingsViewModel: IHandle<TotalTimeChangedMessage>
{
private readonly IEventAggregator eventAggregator;
public SettingsViewModel(IEventAggregator eventAggregator)
{
this.eventAggregator = eventAggregator;
this.eventAggregator.Subscribe(this);
}
public void Handle(TotalTimeChangedMessage message)
{
// Use message.TotalTime as necessary
}
}

Related

Data-binded value won't get updated on UI

I have a class called Commander and it have a property called CurrentRound and it gets updated every few seconds internally. I'm using CommunityToolkit.MVVM and In my viewmodel I declared an observable property called CurrentRound and in the constructor of my viewmodel I did this currentRound = _commander.CurrentRound;.
The strange thing is that when I put a breakpoint in my Commander class and pause the program, I can see the updated value in my xaml file(view) but not on the actual UI.
Commander Class:
public partial class Commander
{
private readonly Communication communication;
public Commander(Communication communication)
{
this.communication = communication;
ThreadStart work = this.NameOfMethodToCall;
Thread thread = new Thread(work);
thread.Start();
}
private async void NameOfMethodToCall()
{
while(true)
{
await Task.Delay(10000);
Console.WriteLine("hey I'm the second thread!");
CurrentRound = GetCurrentRound();
}
}
public int CurrentRound { get; private set; } = 1;
}
ViewModel:
public partial class ParametersViewModel: ObservableObject
{
public ParametersViewModel(Commander commander, ...)
{
_commander = commander;
currentRound = _commander.CurrentRound;
...
}
private readonly Commander _commander;
...
[ObservableProperty]
int currentRound;
...
}
View:
<Label
Text="{Binding CurrentRound, StringFormat='Current Round = {0}'}"
HorizontalOptions="Center"
FontAttributes="Bold"
FontSize="Body"/>
...
UPDATE:
Still having the same issue!
ViewModel:
public partial class ParametersViewModel: ObservableObject
{
...
[ObservableProperty]
Commander myCommander;
...
}
View:
<Label
Text="{Binding MyCommander.CurrentRound, ...
SECOND UPDATE:
This update solved my issue but I should make my Commander class inherits from ObservableObject and make its CurrentRound property an ObservableProperty, but when using MVVM design pattern I should not worry about UI in the models and business logic, but if I do this, I should care about UI bindings in the models right? isn't there a better ways of doing this?
Commander Class:
public partial class Commander: ObservableObject
{
...
[ObservableProperty]
int currentRound = 1;
...
}
Just want to point out something.
Considering:
and in the constructor of my viewmodel I did this currentRound =
_commander.CurrentRound
And:
[ObservableProperty]
int currentRound;
currentRound is a field.
CurrentRound is a property.
Setting fields does not call code. Setting properties does.
All the problems you had, comes from no following naming conventions.
Property A has field _a, not a.

WPF switching views

I am implementing a WPF application and I am switching view models on button click. I had to implement an navigation store by youtube tutorial. When I click a button, navigateCommand will execute, creating a new viewModel and notifying view to change. However I dont understand what is method OnCurrentViewModelChanged() doing and why is it needed, action CurrentViewModelChanged is returning void, and is empty? Or am I missing something? What is CurrentViewModelChanged doing? Can someone please explain?
public class NavigationStore
{
public event Action CurrentViewModelChanged;
private NotifyPropertyChanged currentViewModel;
public NotifyPropertyChanged CurrentViewModel
{
get => currentViewModel;
set
{
currentViewModel = value;
OnCurrentViewModelChanged();
}
}
private void OnCurrentViewModelChanged()
{
CurrentViewModelChanged?.Invoke();
}
}
public class NavigateCommand<TViewModel> : CommandBase where TViewModel : NotifyPropertyChanged
{
private readonly NavigationStore _navigationStore;
private readonly Func<TViewModel> _createViewModel;
public NavigateCommand(NavigationStore navigationStore, Func<TViewModel> createViewModel)
{
_navigationStore = navigationStore;
_createViewModel = createViewModel;
}
public override void Execute()
{
_navigationStore.CurrentViewModel = _createViewModel();
}
}
public class MainViewModel : NotifyPropertyChanged
{
private readonly NavigationStore _navigationStore;
public NotifyPropertyChanged CurrentViewModel => _navigationStore.CurrentViewModel;
public MainViewModel(NavigationStore navigationStore)
{
_navigationStore = navigationStore;
_navigationStore.CurrentViewModelChanged += OnCurrentViewModelChanged;
}
private void OnCurrentViewModelChanged()
{
OnPropertyChanged(nameof(CurrentViewModel));
}
}
So first of all, I also followed his tutorials (it's most likely SingletonSean's) and I don't share #BenicCode's opinion on that (tho I'm not a professional at WPF like he may be), I really like his explanations and solutions to problems. Besides, he keeps changing the project throughout the guide, implementing better solutions and explaining why it's better to use this than that.
The OnCurrentViewModelChanged() method raises an event so that any method that is subscribed to it will be invoked. However, you actually don't need it, you can implement NavigationStore like this:
NavigationStore.cs
public class NavigationStore : INavigationStore
{
private ViewModelBase? _currentViewModel;
public ViewModelBase? CurrentViewModel
{
get => _currentViewModel;
set
{
_currentViewModel?.Dispose();
_currentViewModel = value;
NavigationStateChanged?.Invoke();
}
}
public event Action? NavigationStateChanged;
}
And now, in your MainViewModel, you can simply subscribe the NavigationStateChanged action to OnCurrentViewModelChanged() instead of having one more method in your navigation store.
MainViewModel.cs
public class MainViewModel : ViewModelBase
{
private readonly INavigationStore _navigationStore;
public ViewModelBase? CurrentViewModel => _navigationStore.CurrentViewModel;
public MainViewModel(INavigationStore navigationStore)
{
_navigationStore = navigationStore;
_navigationStore.NavigationStateChanged += OnNavigator_NavigationStateChanged;
}
private void OnNavigator_NavigationStateChanged()
{
OnPropertyChanged(nameof(CurrentViewModel));
}
}
It's basically the same, but a bit simpler (correct me if I'm wrong). By subscribing NavigationStateChanged to OnNavigator_NavigationStateChanged, whenever NavigationStateChanged is raised, OnNavigator_NavigationStateChanged will fire too, which will notify your UI to change (since you bind the ContentControl's Content property to the CurrentViewModel property).
MainWindow.xaml
<Grid>
<ContentControl Content="{Binding CurrentViewModel}" />
</Grid>
At this point of the tutorial he just wanted to demonstrate really basic navigation. As you progress further, things get cleaner and more complicated. I really suggest finishing his tutorials, there might be better guides, but as a starting point, I couldn't find any better channel.

Synchronizing RX .NET Subscribe by CorrelationId

I'm trying to write something similar to Sagas using RX.NET. I've came across a simple issue and I don't know what the best way is to sychronize states by correlation id. I have simple EventAggregator which listens for some events and it can be notified from multiple threads so I cannot assume that subscribe is always called on the same thread so there is a possibility that the first notification will update the state and in the meantime another notification will receive old state and work with it so I have to synchronize it. I have simplified my scenario to something like below. I could lock the whole subscribe by some object id dependent but it doesn't seem right. What is RX way of doing it?
public interface IEventAggregator
{
IObservable<T> GetEvent<T>() where T : Event;
}
public class EventAggregator : IEventAggregator
{
Subject<Event> _sub = new Subject<Event>();
public IObservable<T> GetEvent<T>() where T : Event
{
return _sub.OfType<T>();
}
public void Notify<T>(T ev) where T : Event
{
_sub.OnNext(ev);
}
}
public class Event
{
public string CorrelationId { get; set; }
}
public class State
{
public int SomeValue { get; set; }
}
public interface IStateRepository
{
State Get(string id);
}
public class ProcessManager
{
private readonly IStateRepository _stateRepository;
private readonly IEventAggregator _eventAggregator;
public ProcessManager(IStateRepository stateRepository, IEventAggregator eventAggregator)
{
_stateRepository = stateRepository;
_eventAggregator = eventAggregator;
eventAggregator.GetEvent<Event>()
.Select(x => _stateRepository.Get(x.CorrelationId))
.Subscribe(state =>
{
// -do something with state like write or read;
// - state should be unique per correlation id.
// - this block should be synchronized.
});
}
}

Should a child ViewModel be able to change on Parent ViewModel?

I have a situation and I'm not sure if I'm doing it correct.
I have a ApplicationViewModel that is my "shell" for my whole application.
And within that viewmodel I have other child-ViewModels.
public ApplicationModelView()
{
ModelViewPages.Add(new GameViewModel());
ModelViewPages.Add(new EditGameViewModel());
//Set Current HomePage
CurrentPage = ModelViewPages[0];
}
#endregion
#region Properties
public BaseModelView CurrentPage
{
get { return _currentPage; }
set { _currentPage = value; OnPropertyChanged(); }
}
public List<BaseModelView> ModelViewPages
{
get
{
if (_modelViewPages == null){_modelViewPages = new List<BaseModelView>();}
return _modelViewPages;
}
}
#endregion
In my GameViewModel I display a list of objects from my model GamesModel that contains title,description etc.
When I click on an item in this list it becomes selected and then I want to change my View to EditGameViewModel with a button but I'm not sure if how to do it.
How can I get my child-ViewModel to change content in my parent-ViewModel?
Or should the child even do that?
EDIT
How I want it to function
I want when I select an item and click on button that I change from the view GameViewModel to EditGameViewModel with the data that I have selected from the list.
public void EditGame(object param)
{
//MessageBox.Show("From EditGame Function"); HERE I WANT TO CHANGE THE VIEWMODEL ON MY APPLICATIONVIEWMODEL
}
public bool CanEditGame(object param)
{
return SelectedGame != null;
}
I can offer something that works, but could be questionable. It all really depends on how you plan for your application to function.
First, similar to your MainViewModel, you want something like this:
public class MainViewModel : ObservableObject //ObservableObject being a property change notification parent
{
//Current view will always be here
public BaseViewModel ViewModel { get; set; }
public MainViewModel()
{
//By default we will say this is out startup view
Navigate<RedViewModel>(new RedViewModel(this));
}
public void Navigate<T>(BaseViewModel viewModel) where T : BaseViewModel
{
ViewModel = viewModel as T;
Console.WriteLine(ViewModel.GetType());
OnPropertyChanged("ViewModel");
}
}
Now, since we are navigating this way, every child view needs to derive from BaseViewModel.
public class BaseViewModel : ObservableObject
{
private MainViewModel _mainVM;
public BaseViewModel(MainViewModel mainVM)
{
_mainVM = mainVM;
}
protected void Navigate<T>() where T : BaseViewModel
{
//Create new instance of generic type(i.e. Type of view model passed)
T newVM = (T)Activator.CreateInstance(typeof(T), _mainVM);
//Change MainViewModels ViewModel to the new instance
_mainVM.Navigate<T>(newVM);
}
}
Now we just really need to see how we are going to have child views delegate this call of change. So we will have a BlueView.
public class BlueViewModel : BaseViewModel
{
//Relay command to call 'ToRed' function
public ICommand ChangeToRed
{
get { return new RelayCommand(ToRed); }
}
//Requires MainViewModel for BaseViewModel
public BlueViewModel(MainViewModel main) : base(main)
{
}
//Calling BaseViewModel function. Passed BaseViewModel Type
public void ToRed(object param)
{
Navigate<RedViewModel>();
}
}
And a RedView:
public class RedViewModel : BaseViewModel
{
//Relay command to call 'ToBlue' function
public ICommand ChangeToBlue
{
get { return new RelayCommand(ToBlue); }
}
//Requires MainViewModel for BaseViewModel
public RedViewModel(MainViewModel main) : base(main)
{
}
//Calling BaseViewModel function. Passed BaseViewModel Type
public void ToBlue(object param)
{
Navigate<BlueViewModel>();
}
}
Our MainWindow.xaml could look like this:
<Window.DataContext>
<viewmodels:MainViewModel/>
</Window.DataContext>
<Grid>
<ContentControl Content="{Binding ViewModel}" />
</Grid>
Doing this all children will be able to call to their parent that they would like a change. The BaseViewModel holds this parent for all children who derive, so they can pass it back and forth during navigation like a baton.
Again, Navigation all really depends on how you are using, building and planning for your application. It's not always a one size fits all way.

The same property in two ViewModels and the property changed

I have three ViewModels:
- MainViewModel,
- NavigatorViewModel,
- ProjectViewModel.
In the MainViewModel I have a property called CurrentProject from type ProjectViewModel:
public ProjectViewModel CurrentProject
{
get
{
return _currentProject;
}
set
{
if (_currentProject == value)
{
return;
}
_currentProject = value;
RaisePropertyChanged("CurrentProject");
}
}
In the NavigatorViewModel I have also a property CurrentProject
public ProjectViewModel CurrentProject { get { return ViewModelLocator.DesktopStatic.CurrentProject; } }
I use MVVM light. The View NavigatorView doesnt get notified if the property CurrentProject in the MainViewModel is changed.
How can I let the NavigatorView know, that the property has changed?
As a design concern, I would recommend not using a static Singleton pattern for this. You could use the Messenger class to send messages.
However, to address your current problem, you need to respond to the PropertyChanged event on the Singleton for that property:
public class NavigatorViewModel : INotifyPropertyChanged
{
public NavigatorViewModel()
{
// Respond to the Singlton PropertyChanged events
ViewModelLocator.DesktopStatic.PropertyChanged += OnDesktopStaticPropertyChanged;
}
private void OnDesktopStaticPropertyChanged(object sender, PropertyChangedEventArgs args)
{
// Check which property changed
if (args.PropertyName == "CurrentProject")
{
// Assuming NavigatorViewModel also has this method
RaisePropertyChanged("CurrentProject");
}
}
}
This solution listens for changes to the Singleton property and propagates the change to listeners of NavigatorViewModel.
Warning: Somewhere in the NavigatorViewModel you need to unhook the event or you risk a memory leak.
ViewModelLocator.DesktopStatic.PropertyChanged -= OnDesktopStaticPropertyChanged;

Categories