how to update control on one view from another view in mvvm - mvvm

Hi I have three region based views in my MVVM application. I am new to MVVM and I want to update DataGrid on click on button from different view.
One view have one button and second view have datagrid. I would like to update datagrid result or bind datagrid when button on other view is pressed. I saw few post on doing it with eventService but I am not sure how. cany anyone give me some example to do as I am new so not sure. Thanks in advance.

a simple solution is to use the same ViewModel in both views:
ViewModel:
public class MyModel : ViewModel
{
static myModel;
public static MyModel Current { get { if(myModel==null) myModel=new MyModel(); return myModel; } }
public IEnumerable<T> Data { get { ... } set { /* Notification */ }}
public ICommand SetData {get { return new DelegateCommand(()=>Data= /* return the data */); }
}
Button view:
<Button Command={Binding SetData} />
DataGrid view:
<DataGrid ItemsSource={Binding Data} />
In the code-behind of both views, add to the constructor:
this.DataContext = MyModel.Current;

Related

Change a ViewModel Property from a different class and update the View - MVVM

I need to change the Visibility of a Button on a View from method call from within a class.
I have tried accessing the VeiwModel by exposing it in the class, and then had success in changing the Property "ShowRedHat" from true to false, but this does not update the Visibility of the Button in the View. This also double loads the ViewModel, which is not acceptable in my solution.
Any help is appreciated.
The class:
public class HatEngine
{
public void SetShowRedHat()
{
????.ShowRedHat = false;
}
}
The Property in the ViewModel:
public class MyViewModel : ObservableObject
{
private bool _showRedHat;
public bool ShowRedHat
{
get { return _showRedHat; }
set
{
OnPropertyChanged(ref _showRedHat, value);
}
}
}
The Button in the View:
<Button Content="Red Hat"
Command="{Binding RedHatCommand}"
Visibility="{Binding ShowRedHat, Converter={StaticResource BoolToVis}}"/>
If the purpose of HatEngine is to be a service that is used by MyViewModel, then something like the following be the start of getting what you need.
This example uses dependency injection via the constructor; this is common in MVVM and if you're not familiar with it, I would highly recommend looking into it further.
// define delegate for event to be fired from HatEngine instances
public delegate void HatEngineNotifyEventHandler(object sender, bool shouldShow);
// interface declaration for HatEngine - this is important for injecting mocks for unit testing
public interface IHatEngine
{
event HatEngineNotifyEventHandler Notify;
void SetShowRedHat(bool show);
}
// simple IHatEngine implementation
public sealed class HatEngine : IHatEngine
{
public event HatEngineNotifyEventHandler Notify;
public void SetShowRedHat(bool show) => OnNotify(show);
private void OnNotify(bool shouldShow) =>
Notify?.Invoke(this, shouldShow);
}
public class MyViewModel : ObservableObject
{
private readonly IHatEngine _hatEngine;
private bool _showRedHat;
// MyViewModel consumes an IHatEngine instance and subscribes to its Notify event
public MyViewModel(IHatEngine hatEngine = null)
{
// many MVVM frameworks include a DI container that should be used here
// to resolve an IHatEngine instance; however, for simplicity for this
// example just create HatEngine() directly
_hatEngine = hatEngine ?? new HatEngine();
// when the event is received, update ShowRedHat accordingly
_hatEngine.Notify += (_, shouldShow) => ShowRedHat = shouldShow;
}
public bool ShowRedHat
{
get => _showRedHat;
set => OnPropertyChanged(ref _showRedHat, value);
}
}
You can just bind an integer since Visibility is an Enum, check documentation since in some versions Hidden option is not available and Collapsed becomes 1, however normally you can just use these below:
Visible [0] - Display the element.
Hidden [1] Do not display the element, but reserve space for the
element in layout.
Collapsed [2] Do not display the element, and do not reserve space for
it in layout.

NullReferenceException When binding List<View> between Control and ViewModel in Xamarin.Forms

I have a problem with binding List of View elements between control and viewModel.
I created a ContentView and included it in a ContentPage. I need to create Grid.Children within ContentView dynamically by adding to the Grid a List of BoxView elements created within the ViewModel.
I managed to bind different control(e.g. Label) successfully, however when I attempt to bind the List of Views, I get an error.
First error appears when navigating to the ContentPage containing ContentView, the page doesn't event load and I get:
System.NullReferenceException: Object reference not set to an instance of an object.
Your app has entered a break state, but there is no code to show because all threads were executing external code (typically system or framework code).
So, at this point I removed binding from from the Control(markup inside ContentPage's XAML) and page loads, but when I launch method that is supposed to populate Control's Grid with List containing BoxView elements I will not be able to pass the List to the Control, as the binding is not present at this point.
My question is, is there a way to bind List of Views between ViewModel and ContentView in Xamarin.Forms? Or am I trying to do something that will never work?
How would I refresh the Grid of ContentView as the Grid is being rendered based on List of Views created after ContentPage is loaded?
If the above is not posssible, is creating Grid's children upon ContentPage's load only option? Would it be possible then to pass List of Views as an argument to ContentPage?
My general problem here is that I need to generate Grid's children based on data I will input later based on various factors.
I hope it is possible to render/refresh Grid and it's Children after the Content Page is actually loaded.
Here is the code I have for now:
ContentView Code Behind
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class GridView : ContentView
{
public GridView()
{
InitializeComponent();
}
public static readonly BindableProperty ViewProperty = BindableProperty.Create(nameof(ViewList), typeof(List<View>), typeof(GridView));
public List<View> ViewList
{
get
{
return (List<View>)GetValue(ViewProperty);
}
set
{
SetValue(ViewProperty, value);
}
}
}
ContentView XAML
<AbsoluteLayout>
<Grid x:Name="RenderedGrid"
x:FieldModifier="public"
ColumnSpacing="1"
AbsoluteLayout.LayoutBounds="0, 0, 1, 1"
AbsoluteLayout.LayoutFlags="All">
</Grid>
</AbsoluteLayout>
ContentPage code behind
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class GridPage : ContentPage
{
public GridPage ()
{
InitializeComponent ();
BindingContext = new GridPageViewModel(Navigation);
}
}
ContentPage Markup
<ScrollView Grid.Row="1">
<local:GridView ViewList="{Binding Path=BindingContext.ViewsList, Source={x:Reference GridPage}}"/>
</ScrollView>
ContentPage's ViewModel
[XamlCompilation(XamlCompilationOptions.Compile)]
public class GridPageViewModel : BaseGridViewModel
{
public Classes _Classes;
public GridView _gridView = new GridView();
public ICommand RenderGridCommand { get; private set; }
public GridPageViewModel(INavigation navigation)
{
_navigation = navigation;
RenderGridCommand = new Command(() => StartRender());//used when button is hit, not when page loads.
}
public void StartRender()
{
CreateGrid();
}
public void CreateGrid()
{
//List<View> views = new List<View>();
for (int i = 0; i < SomeProvidedCustomDataList.Count(); i++)
{
BoxView boxView = new BoxView
{
Color = Color.Accent,
HeightRequest = SomeProvidedCustomDataList[i].Height,
VerticalOptions = LayoutOptions.End,
StyleId = SomeProvidedCustomDataList[i].Name
};
boxView.GestureRecognizers.Add(tapGesture);
ViewsList.Add(boxView);
}
//Add list of boxViews to Grid
_gridView.RenderedGrid.Children.AddHorizontal(_gridView.ViewList);
}
List<View> _viewsList = new List<View>();
public List<View> ViewsList
{
get => _viewsList;
set
{
_viewsList = value;
NotifyPropertyChanged("ViewsList");
}
}
}

Update DataGrid on item change with Caliburn Micro

I have a datagrid which is bound to a collection of items using Caliburn Micro. I would like the grid to update as soon as a user makes an edit on each row. I would think this would be simple (like ASP.NET simple) but I haven't found anything that seems to work.
Here is my ViewModel
public class JournalViewModel : Caliburn.PresentationFramework.PropertyChangedBase
{
private CrystalRptDataEntities ctx = new CrystalRptDataEntities();
private BindableCollection<EmployeeInfo> employees;
public JournalViewModel()
{
Load();
}
public void Load()
{
employees = new BindableCollection<EmployeeInfo>(ctx.EmployeeInfoes);
AllEmployees = employees;
}
public BindableCollection<EmployeeInfo> AllEmployees
{
get { return employees; }
set
{
employees = value;
NotifyOfPropertyChange(() => AllEmployees);
}
}
//....
}
Here is my view
<DataGrid x:Name="AllEmployees" AutoGenerateColumns="True" />
I found the solution to my own question - it took 3 things.
1) I had to add this method to my JournalViewModel class
public void SaveChanges()
{
ctx.SaveChanges();
}
2) Then I had to add these 2 references to my xaml file
xmlns:i="clr-namespace:System.Windows.Interactivity;
assembly=System.Windows.Interactivity"
xmlns:cal="http://www.caliburnproject.org"
3) Then I had to attach an Event to my Datagrid like this:
<DataGrid x:Name="AllEmployees"
AutoGenerateColumns="True"
cal:Message.Attach="[Event CellEditEnding]=[Action SaveChanges()]">
That way every time I finished editing a cell, the ctx gets saved.

Dynamic VIew? where to put logic?

I am currently programming using the MVVM pattern.
My view Model Looks like so
class DoorsViewModel
{
ObservableCollection<Door> doorCollection;
};
the door class looks like the following
class Door
{
string name;
bool isOpen;
};
my view is linked to the viewmodel, and simply contains a longlistselector with a picture and the name of the door. I want the picture to be dynamic and change depending on the state of the door( whether it's open or closed ). How would i implement it so that the picture updates dynamically depending on the state of the door? should this be done in the viewmodel? or should it be done within the view?
This logic should be in the ViewModel. All logic that is related to the view or how things are displayed should be in the ViewModel. No logic should be in the view (.xaml.cs).
You typically use the INotifyPropertyChanged interface to notify the View that something has changed. In this case you want a door image to be changed when the state of the door changes. In this case I would try something like this.
class Door: INotifyPropertyChanged
{
private string _name;
private bool _isOpen;
public Uri DoorImage
{
get
{
if (_isOpen) return new Uri("uri_to_open.png");
return new Uri("uri_to_closed.png");
}
}
public bool IsOpen
{
get { return _isOpen; }
set
{
_isOpen = value;
RaisePropertyChanged("IsOpen");
// important, notifies the UI to update the door image
RaisePropertyChanged("DoorImage");
}
}
private void RaisePropertyChanged(string propertyName)
{
var tmp = PropertyChanged;
if (tmp != null) tmp(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
};
Note: I have encapsulated the fields into properties.
If your image is embedded in your assebmly, please check out this link to learn how to write an uri for your image.

MVVM using Page Navigation On Windows Phone 7

The Navigation framework in Windows Phone 7 is a cut down version of what is in Silverlight. You can only navigate to a Uri and not pass in a view. Since the NavigationService is tied to the View, how do people get this to fit into MVVM. For example:
public class ViewModel : IViewModel
{
private IUnityContainer container;
private IView view;
public ViewModel(IUnityContainer container, IView view)
{
this.container = container;
this.view = view;
}
public ICommand GoToNextPageCommand { get { ... } }
public IView { get { return this.view; } }
public void GoToNextPage()
{
// What do I put here.
}
}
public class View : PhoneApplicationPage, IView
{
...
public void SetModel(IViewModel model) { ... }
}
I am using the Unity IOC container. I have to resolve my view model first and then use the View property to get hold of the view and then show it. However using the NavigationService, I have to pass in a view Uri. There is no way for me to create the view model first. Is there a way to get around this.
Instead of passing the view through the constructor. You could construct the view first via the NavigationService and pass it into the view-model. Like so:
public class ViewModel : IViewModel
{
private IUnityContainer container;
private IView view;
public ViewModel(IUnityContainer container)
{
this.container = container;
}
public ICommand GoToNextPageCommand { get { ... } }
public IView
{
get { return this.view; }
set { this.view = value; this.view.SetModel(this); }
}
public void GoToNextPage()
{
// What do I put here.
}
}
PhoneApplicationFrame frame = Application.Current.RootVisual;
bool success = frame.Navigate(new Uri("View Uri"));
if (success)
{
// I'm not sure if the frame's Content property will give you the current view.
IView view = (IView)frame.Content;
IViewModel viewModel = this.unityContainer.Resolve<IViewModel>();
viewModel.View = view;
}
If you are using Mvvm Light you could try:
Windows Phone 7 — Navigation between pages using MVVM Light Messaging
(See similar post: Silverlight Navigation using Mvvm-light(oobe)+MEF?)
My opinion is that the view-model should be created and registered at application startup. By placing it inside the root DataContext all pages will automatically get a reference to it without any code-behind or IoC tricks.
// Code to execute when the application is launching (eg, from Start)
// This code will not execute when the application is reactivated
private void Application_Launching(object sender, LaunchingEventArgs e)
{
m_ViewModel = new PrimaryViewModel(RootFrame) ;
RootFrame.DataContext = m_ViewModel;
}
// Code to execute when the application is activated (brought to foreground)
// This code will not execute when the application is first launched
private void Application_Activated(object sender, ActivatedEventArgs e)
{
m_ViewModel = new PrimaryViewModel(RootFrame) ;
m_ViewModel.Activated(PhoneApplicationService.Current.State);
RootFrame.DataContext = m_ViewModel;
}
If you are using MVVM architecture,then you can pass navigationPage after registering using Messenger. Create a model class (say NavigateToPageMessage) with a string(say PageName) variable. You want to pass string from homepage.xaml to newpage.xaml,then in Homepage viewmodel just send the message like this under the command you binded (say HomeNavigationCommand)
private void HomeNavigationCommandHandler()
{
Messenger.Default.Send(new NavigateToPageMessage {PageName = "newpage"});
}
In the newpage Viewmodel,you should register the messenger like this,
Messenger.Default.Register<NavigateToPageMessage>(this, (action) => ReceiveMessage(action));
private object ReceiveMessage(NavigateToPageMessage action)
{
var page = string.Format("/Views/{0}.xaml", action.PageName);
NavigationService.Navigate(new System.Uri(page,System.UriKind.Relative));
return null;
}
//Assuming your views are in View Folder