I see example on this link, how to switch between two views. Easiest sollution and perfect for my application - I will also have only two views.
So we have one parent View (MainWindow) and two children Views. Parent View have dedicadet two buttons to swtich between this two Views ("First View" and "Second View") which are located in "DockPanel".
My question is how to use any button in "First View" to switch to the second View and in "Second View" button to come back to the "First View". What I want is get rid of DockPanel and use buttons from View.
Please for advices, how to do that. If any question please ask. THANKS!
You can use an event from each child viewmodel to signal the parent to change views. So in the code below ButtonOnViewModel1Command is pressed on View1 (which is bound to ViewModel1) which raises the SwitchViewModel2Request event. The MainViewModel subscribes to this event and switches the CurrentViewModel collection to ViewModel2. You can do this same thing on ViewModel2 to switch back to ViewModel1.
public class MainViewModel
{
private ViewModel1 _viewModel1 = new ViewModel1();
private ViewModel2 _viewModel2 = new ViewModel2();
public MainViewModel()
{
//event from ViewModel1
_viewModel1.SwitchViewModel2Request += NavigateToView2;
}
//switch View to ViewModel2
private void NavigateToView2(object sender, EventArgs e)
{
CurrentViewModel = _viewModel2;
}
}
public class ViewModel1
{
public ViewModel1()
{
ButtonOnViewModel1Command = new RelayCommand(Button1Method);
}
//some button on child view 1
public RelayCommand ButtonOnViewModel1Command { get; set; }
private void Button1Method(object obj)
{
OnSwitchViewModel2Request();
}
//event that MainViewModel will subscribe to
public event EventHandler SwitchViewModel2Request = delegate { };
private void OnSwitchViewModel2Request()
{
SwitchViewModel2Request(this, EventArgs.Empty);
}
}
Since you're using MVVM light you should look to using the messenger system ( Good tutorial here ). A simple way would be on first view to send a NotificationMessage as follows:
Messenger.Default.Send<NotificationMessage>(new NotificationMessage("GotoSecondView"));
Then in your main window you would register to receive it as follows:
Messenger.Default.Register<NotificationMessage>(this, NotificationReceived);
Followed by a function to handle them:
private void NotificationReceived(NotificationMessage message)
{
string notice = message.Notification;
switch (notice)
{
case "GotoSecondView":
ExecuteSecondViewCommand
break;
}
}
Repeat the same idea for the other view and add it to your switch. Then you can trigger from anywhere and mainview will handle the change without directly having to link your viewmodels.
Related
I setup a very simple app to get me started and trying things out. It has a label and two buttons. The buttons are linked to relaycommands in the main view model are used to send a message to a server using Mqtt which work as intended. The label is used to show part of the data received from the server. Everything seems to work fine except the labels won't update as soon as the message is received event though I can see in debugging that the property is set. The label will update as soon as I press one of the two buttons...
I'm new to the whole Xamarin android thing and have used mvvm light once in a WPF application.
Main Activity :
public partial class MainActivity
{
// UI Elements
public TextView ScanInfoLabel { get; private set; }
public Button UnlockButton { get; private set; }
public Button RegisterButton { get; private set; }
// Keep track of bindings to avoid premature garbage collection
private readonly List<Binding> _bindings = new List<Binding>();
// Get view model
private MainViewModel mainViewModel { get { return App.Locator.Main;}}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set view from the "main" layout resource
SetContentView(Resource.Layout.Main);
// Get the UI elements by ID
ScanInfoLabel = FindViewById<TextView>(Resource.Id.ScanInfoLabel);
UnlockButton = FindViewById<Button>(Resource.Id.UnlockButton);
RegisterButton = FindViewById<Button>(Resource.Id.RegisterButton);
// Set Bindings for textviews
_bindings.Add(
this.SetBinding(
() => mainViewModel.ScanInfoLabel,
() => ScanInfoLabel.Text));
// Set the bindings for commands
UnlockButton.SetCommand("Click", mainViewModel.UnlockCommand);
RegisterButton.SetCommand("Click", mainViewModel.RegisterTagCommand);
}
In Main view model :
// RelayCommands
public RelayCommand UnlockCommand;
public RelayCommand RegisterTagCommand;
public RelayCommand MqttConnectCommand;
// Bindable properties
private string _scanInfoLabel = "Test";
public string ScanInfoLabel
{
get { return _scanInfoLabel; }
set { Set(ref _scanInfoLabel, value); }
}
// New scan message received
private void RFIDScanReceived(RFID.Scan scan)
{
ScanInfoLabel = BitConverter.ToString(scan.UID);
}
I would expect the label to show the data as soon as the mqtt message is received (which is then sent to the mainviewmodel using Messenger.Default.send<>() from mvvm light). But nothing is changed in the UI until I click on one of the buttons and then the correct information in displayed.
I don't really know where to start being new to xamarin android and none of my searches seemed to be of any help.
Any help will be appreciated, thanks!
ViewModels generally implement the INotifyPropertyChanged interface,
which means that the class fires a PropertyChanged event whenever one
of its properties changes. The data binding mechanism in Xamarin.Forms
attaches a handler to this PropertyChanged event so it can be notified
when a property changes and keep the target updated with the new
value.
Solution:
Make you model inherit from INotifyPropertyChanged and add PropertyChanged inside the set part. Then the labels will update as soon as the value of ScanInfoLabel changed.
public class BaseViewModel : INotifyPropertyChanged
{
// Bindable properties
private string _scanInfoLabel = "Test";
public string ScanInfoLabel
{
get { return _scanInfoLabel; }
set
{
_scanInfoLabel = ScanInfoLabel;
PropertyChanged(this, new PropertyChangedEventArgs("ScanInfoLabel"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
You can refer:data-bindings-to-mvvm
Well I found out it was a threading issue since I set the property from a Messenger call. This apparently is not an issue in WPF which is why I was a bit stuck but using the DispatcherHelper did the trick.
// New scan message received
private void RFIDScanReceived(RFID.Scan scan)
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
ScanInfoLabel = BitConverter.ToString(scan.UID);
});
}
I have seen many examples over the net but I am not able to figure out what i am doing wrong here.
I need to close a view from a view model. Below is the code i have tried:
ViewModel:
public class ViewModel
{
public event EventHandler RequestClose;
public ViewModel()
{
}
}
//Calling the event from view model to close the view from a method in View Model
This event is called on a button click
private void Download()
{
//Download Logic
if(RequestClose != null)
RequestClose(this,EventArgs.Empty);
}
View:
ViewModel vm = new ViewModel();
vm.RequestClose += delegate(object sender, EventArgs args) {this.Close();}
this.DataContext = vm;
You fire the RequestClose event in the ViewModel constructor which is too early to be catched by the event registration.
The best MVVM solution is to use an attached behavior, as outlined in the top rated answer to this question How should the ViewModel close the form?
I faced a similar problem earlier, and did the following: In the viewmodel, create a command that you can bind to (I personally use MvvmLight and its RelayCommand)
public class ViewModel
{
public RelayCommand<object> CloseWindowCommand {get; private set;}
public ViewModel()
{
CloseWindowCommand = new RelayCommand<object>(CloseTheWindow);
}
private void CloseWindow(object obj)
{
var window = obj as Window;
if(window != null)
window.Close();
}
}
In my view, I have button that triggers this command, e.g.
Button Content="Close" Command="{Binding CloseWindowCommand}" CommandParameter="{Binding ElementName=NameOvViewModel}"
I realize now, that this may require the use of MvvmLight, but I hope it offers some guidance on a possible solution to your question.
I'm building a WPF MVVM application but I'm having problems updating a parent view from a child view model.
I call the doAction() method on my parent view model which updates a property and raises a PropertyChangeEvent. When this method is called from the MainViewModel everything works great however when I call the same method from my child view model the PropertyChangedEvent get's raised but the view doesn't update.
Example:
ChildViewModel()
{
private ParentViewModel parent;
parent.doAction(); // Raised event but MainView doesn't update
}
ParentViewModel()
{
public void doAction()
{
this.Property = true;
OnPropertyChange("Property");
}
}
My Views are created using XAML:
<MainView>
<TabItem>
<view:ChildView/>
</TabItem>
</MainView>
Propery Change event is raised like so:
protected void OnPropertyChanged(string name)
{
LOGGER.Info("Property Changed: " + name);
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
My question is how do I get the parent view to listen and update to a property change event raised by a child view.
Edit:
Base Class:
public abstract class AbstractBaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ICommand CloseCommand { get; set; }
public AbstractBaseViewModel()
{
this.CloseCommand = new CloseCommand(this);
}
public void CloseWindow()
{
Application.Current.MainWindow.Close();
}
protected void OnPropertyChanged(string name)
{
LOGGER.Info("Property Changed: " + name);
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
Parent ViewModel:
public class ParentViewModel : AbstractBaseViewModel
{
private Dispatcher dispatcher;
private bool visible;
public bool Visible
{
get { return visible; }
set { visible= value; OnPropertyChanged("Visible"); }
}
public MainWindowViewModel()
{
this.dispatcher = Dispatcher.CurrentDispatcher;
this.manager = manager;
}
public void ShowTab(){
this.Visible = true;
}
}
Child View Model:
public class ChildViewModel : AbstractBaseViewModel
{
private ParentViewModel parentVm;
public GeneralViewModel(ParentViewModel vm)
{
this.parentVm= vm;
}
public void Command(){
vm.ShowTab();
}
}
ParentView Xaml:
<TabItem Header="ViewWeWantToHide/Show"
Visibility="{Binding Visible,Converter={converter:BooleanToVisibilityConverter}}">
<views:SomeOtherView/>
</TabItem>
<TabItem Header="ChildView Tab">
<views:ChildView/>
</TabItem>
Without seeing all of your code, it'd be hard to guess what is causing your problem. However, I'd have to say that it is much more common in WPF to display view model instances in the UI and have WPF automatically display the relevant views, rather than displaying the views directly as you have. If you use view models rather than views then you'll have access to the child view model(s) from the parent view models.
If you did, or do have access to the child view model from the parent view model then I would advise that you use a simple delegate to 'pass your message' from the child view model to the parent view model instead. You can even define a parameter-less delegate and use it to just send a signal, rather than any property value.
You can declare a delegate, add a property of that type to your child UserControl and attach a handler in the main view model. Then you can call the delegate from your child UserControl and in the handler in the parent view model, you can call your method. For more information on this technique, please see my answer to the Passing parameters between viewmodels question (which answers a similar, but not exactly the same problem).
Thank you everyone for your assistance.
I found the issue which was unrelated to WPF and was actually a product of how I was setting the datacontext on my child views. In my parent window I was creating and registering a singleton instance of my ParentViewModel with the Unity container. This instance would then be injected in to all child views, the problem was the InitializeComponent method was being called before my parent view model was created and registered with the unity container meaning all child views were receiving completely a different instance.
Not working:
public MainWindow()
{
InitializeComponent();
if (DesignerProperties.GetIsInDesignMode(this))
{
this.DataContext = new ParentViewModel();
}
else
{
IUnityContainer container = UnityFactory.Retrieve();
ParentViewModel parentVM = container.Resolve<ParentViewModel>();
container.RegisterInstance<ParentViewModel>(parentVM);
this.DataContext = parentVM;
}
}
Working:
public MainWindow()
{
if (DesignerProperties.GetIsInDesignMode(this))
{
this.DataContext = new ParentViewModel();
}
else
{
IUnityContainer container = UnityFactory.Retrieve();
ParentViewModel parentVM = container.Resolve<ParentViewModel>();
container.RegisterInstance<ParentViewModel>(parentVM );
this.DataContext = parentVM;
}
/**
* Initialize after registering parent VM and setting the datacontext
*/
InitializeComponent();
}
I Have UserControl called "Footer.xaml" and "Header.xaml" Both User Control are place to different window.
Footer.xaml have two button :-
btnBasic
btnStandard
Header.xaml have one button :-
lobby
When i click on Lobby button from the Header.xaml i want to change the IsEnabled property of the both button [ btnBasic and btnStandard ] on my condition.
I Try the below things [ Footer.xaml.cs ] by default the both button IsEnabled = true
public partial class Footer : UserControl
{
static Footer objFooter = new Footer();
public Footer()
{
InitializeComponent();
objFooter = this;
}
public static Footer GetFooterInstance()
{
return objFooter;
}
}
and on Header.xaml.cs
private void btnLobby_Click(object sender, RoutedEventArgs e)
{
Footer objFooter;
objFooter = Footer.GetFooterInstance();
objFooter.btnBasic.IsEnabled = false;
objFooter.btnStandard.IsEnabled = false;
}
But nothings is effect with button.
You tagged your question for MVVM but posted code is completely violating the rules of MVVM here. You can achieve this by stricting to the rules of MVVM in following manner -
Create a ViewModel class which will serve as DataContext for both of your views.
Create a bool property inside it and bind IsEnabled DP for your buttons namely btnBasic and btnStandard with this property.
Create an ICommand in your ViewModel class which will be invoked on lobby button click and will set this bool property to true or false depending on your situation.
But as you posted in comment above, you already have seperate ViewModels for both Views, you can use Event Aggregator to communicate between two ViewModels.
I am trying to implement MVVM (Model View ViewModel) pattern for my WinForms application. I am using C# 2005.
My application has a MainForm (View) with 2 multi line textboxes and 3 buttons. The purpose of the 1st textbox is to show a running commentary of what the application is doing, when the button is clicked. I keep on appending lines to the TextBox to update the user what is happening. The purpose of the 2nd textbox is to update the user about any error condition, conflicts, duplicate values; in short, anything which is required by the user to review. It classifies each message as either an INFO or a WARNING or an ERROR. Each of the 3 buttons perform an action, and keeps updating the 2 textboxes.
I have created a MainFormViewModel class.
1st question:
When the user clicks on the button in MainForm, I have to clear the contents of the 2 textboxes, and disable the button so that it cant be clicked again until 1st operation is completed. Should I do this textbox and button updation directly in the MainForm or I should use MainFormViewModel in some way?
2nd question:
The button click calls a method on the MainFormViewModel class. Before calling the method and after calling the method, I want to show a message in the 1st textbox something like "Operation A started / ended". I do this by calling a Common class which has a Log method to log messages to a TextBox or a file or both. Again whether it is ok to do this directly from the MainForm? I call this logging method at the start and end of the event handler.
3rd question:
How do I propagate error messages from ViewModel back to View? I have created a custom Exception class "TbtException". So do I have to write 2 catch blocks in each and every button, one for TbtException and other for genetic Exception class?
Thanks.
You should perform operations in the view only with regard to the state of the ViewModel object. E.g. you shouldn't assume the view model is calculating when you click a button, but you should add a state to the view model that says it's doing something longer and then recognize that state in the view. You shouldn't disable or enable buttons in the view as you please, but only if there's a state that demands these buttons to be changed. This can go as far as to have a property that indicates which item in a list is currently selected, so the UI doesn't call the list control's SelectedItem member, but the viewmodel's. And when the user clicks remove, then the view model will remove the selected member from its list and the view is automatically updated through state changes in the form of events.
Here's what I would call a view model for your view. It exposes messages through an observable collection to which the view can bind (ie. register event handlers, since binding is not well supported in WinForms). The textbox at any time renders only the contents of the collection. It has actions for clearing those collections which your view can call. The view can also call actions of the underlying model, but it should be updated through the viewmodel only! The view should never register any event handlers for events exposed by the underlying model. If you ever want to do that you should hook up that event in the view model and expose it to the view there. Sometimes that may feel like "just another level of indirection" which is why it may be overkill for very simple applications such as yours.
public class MainFormViewModel : INotifyPropertyChanged {
private object syncObject = new object();
private MainFormModel model;
public virtual MainFormModel Model {
get { return model; }
set {
bool changed = (model != value);
if (changed && model != null) DeregisterModelEvents();
model = value;
if (changed) {
OnPropertyChanged("Model");
if (model != null) RegisterModelEvents();
}
}
}
private bool isCalculating;
public bool IsCalculating {
get { return isCalculating; }
protected set {
bool changed = (isCalculating != value);
isCalculating = value;
if (changed) OnPropertyChanged("IsCalculating");
}
}
public ObservableCollection<string> Messages { get; private set; }
public ObservableCollection<Exception> Exceptions { get; private set; }
protected MainFormViewModel() {
this.Messages = new ObservableCollection<string>();
this.Exceptions = new ObservableCollection<string>();
}
public MainFormViewModel(MainFormModel model)
: this() {
Model = model;
}
protected virtual void RegisterModelEvents() {
Model.NewMessage += new EventHandler<SomeEventArg>(Model_NewMessage);
Model.ExceptionThrown += new EventHandler<OtherEventArg>(Model_ExceptionThrown);
}
protected virtual void DeregisterModelEvents() {
Model.NewMessage -= new EventHandler<SomeEventArg>(Model_NewMessage);
Model.ExceptionThrown -= new EventHandler<OtherEventArg>(Model_ExceptionThrown);
}
protected virtual void Model_NewMessage(object sender, SomeEventArg e) {
Messages.Add(e.Message);
}
protected virtual void Model_ExceptionThrown(object sender, OtherEventArg e) {
Exceptions.Add(e.Exception);
}
public virtual void ClearMessages() {
lock (syncObject) {
IsCalculating = true;
try {
Messages.Clear();
} finally { IsCalculating = false; }
}
}
public virtual void ClearExceptions() {
lock (syncObject) {
IsCalculating = true;
try {
Exceptions.Clear();
} finally { IsCalculating = false; }
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropetyChanged(string property) {
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(property));
}
}
EDIT: Regarding exception handling
I would rather catch exceptions in the ViewModel than in the view. The view model is better suited to prepare them for display. I don't know how that works in WPF though. I've yet to program an application in WPF, we're doing a lot of WinForms still.
Opinions may vary, but I think generic try/catch clauses aren't really exception handling. I think you should rather test your UI very well and include exception handling only when necessary. Which is why you unit test your view model and user test the view. However if you really stick to the principle and avoid logic in the view, you can do a lot with unit tests.