When I navigate from a page to another I am passing an object of class and adding to list view. But the problem I am facing is that each time when the page is navigated the item is added in list box but the item that was previously added gets removed.
Can anyone help me to solve this issue and how I can retrieve the previous listbox item?
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if ((App.Current as App).isThereAnyChange)
{
getitem();
changeUI();
}
}
private void changeUI()
{
//throw new NotImplementedException();
txt1.Text = "";
items.Add(item1);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
this.sniplist.ItemsSource = items;
});
}
private void getitem()
{
// throw new NotImplementedException();
item1 = PhoneApplicationService.Current.State["item"] as Item;
// Debug.WriteLine(item1.description);
}
Keep a static item in your ....xaml.cs file
static List<object> listSrc = new List<object>();
override OnNavigatedTo()
{
listSrc.Add("whatever you want");
sniplist.ItemsSource= listSrc;
}
Here a simple setup:
public class MainViewModel
{
public ObservableCollection<Item> Items { get; private set; }
public MainViewModel() { Items = new ObservableCollection<Item>(); }
}
in App.xaml.cs:
public partial class App : Application
{
private static MainViewModel viewModel = new MainViewModel();
public static MainViewModel ViewModel { get { return viewModel; } }
...
And then you can access that class from any page simply with App.ViewModel, and it will persist through page navigations.
Related
I am tying to further understand MVVM with some example scenario. I have a rootpage with a 'maindisplay' textblock. I would like to display 'status' or 'scenarios' from activation of any form of UI eg. togglebutton on the 'maindisplay' textblock.
I am able to bind the the page navigation info in the rootpageviewmodel to the textblock. However, I am not able to achieve the result when displaying info from different page.
I have checked another post multiple-viewmodels-in-same-view & Accessing a property in one ViewModel from another it's quite similar but it didn't work.
Please help. Thanks.
While accessing the RootPageViewModel should retain the instance?
View
<TextBlock Text="{x:Bind RootViewModel.MainStatusContent, Mode=OneWay}"/>
RootPage.xaml.cs
public sealed partial class RootPage : Page
{
private static RootPage instance;
public RootPageViewModel RootViewModel { get; set; }
public RootPage()
{
RootViewModel = new RootPageViewModel();
this.InitializeComponent();
// Always use the cached page
this.NavigationCacheMode = NavigationCacheMode.Required;
}
public static RootPage Instance
{
get
{
if (instance == null)
{
instance = new RootPage();
}
return instance;
}
}
private void nvTopLevelNav_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
{
if (args.IsSettingsInvoked)
{
contentFrame.Navigate(typeof(SettingsPage));
RootViewModel.MainStatusContent = "Settings_Page";
}
else
{
var navItemTag = args.InvokedItemContainer.Tag.ToString();
RootViewModel.MainStatusContent = navItemTag;
switch (navItemTag)
{
case "Home_Page":
contentFrame.Navigate(typeof(HomePage));
break;
case "Message_Page":
contentFrame.Navigate(typeof(MessagePage));
break;
}
}
}
}
RootPage ViewModel:
public class RootPageViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private static RootPageViewModel instance = new RootPageViewModel();
public static RootPageViewModel Instance
{
get
{
if (instance == null)
instance = new RootPageViewModel();
return instance;
}
}
public RootPageViewModel()
{
}
private string _mainStatusContent;
public string MainStatusContent
{
get
{
return _mainStatusContent;
}
set
{
_mainStatusContent = value;
OnPropertyChanged();
}
}
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
MessagePage.xaml.cs - to access RootPage ViewModel
public sealed partial class MessagePage : Page
{
public MessagePageViewModel MessageViewModel { get; set; }
public MessagePage()
{
MessageViewModel = new MessagePageViewModel();
this.InitializeComponent();
// Always use the cached page
this.NavigationCacheMode = NavigationCacheMode.Required;
}
private void Message1_Checked(object sender, RoutedEventArgs e)
{
RootPageViewModel.Instance.MainStatusContent = "Message 1 Selected";
}
private void Message1_Unchecked(object sender, RoutedEventArgs e)
{
RootPageViewModel.Instance.MainStatusContent = "Message 1 De-Selected";
}
}
When I debug the value did write to the instance but did't update the TextBlock. Did I do anything wrong in my XAML binding?
UWP C# MVVM How To Access ViewModel from Other Page
The better way is make static variable for RootPage, but not make singleton instance for RootPage and RootPageViewModel.
For example:
public RootPage ()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
Instance = this;
RootViewModel = new RootPageViewModel();
}
public static RootPage Instance;
Usage
private void Message1_Checked(object sender, RoutedEventArgs e)
{
RootPage.Instance.RootViewModel.MainStatusContent = "Message 1 Selected";
}
private void Message1_Unchecked(object sender, RoutedEventArgs e)
{
RootPage.Instance.RootViewModel.MainStatusContent = "Message 1 De-Selected";
}
I'm completely new to WPF and I'm having problems with ItemsSource updates.
I created a single main window Metro application with tabs (TabItem(s) as UserControl DataContext="{Binding}") in which different data is displayed / different methods used.
What I've found myself struggling with is INotifyPropertyChanged (I wasn't able to understand the solution of my problem from similar examples/questions) interface's concept. I'm trying to make that if new data is entered in a window (which is initialized from one of the UserControl), a ComboBoxin another UserControl (or TabItem) would be automatically updated. Here's what I have:
UserControl1.xaml
public partial class UserControl1: UserControl
{
private userlist addlist;
public UserControl1()
{
InitializeComponent();
fillcombo();
}
public void fillcombo()
{
Fillfromdb F = new Fillfromdb(); // class that simply connects
// to a database sets a datatable as ListCollectionView
addlist = new addlist { List = F.returnlistview() }; // returns ListCollectionView
UsersCombo.ItemsSource = addlist.List;
}
userlist.cs
public class userlist: INotifyPropertyChanged
{
private ListCollectionView _list;
public ListCollectionView List
{
get { return this._list; }
set
{
if (this._list!= value)
{
this._list= value;
this.NotifyPropertyChanged("List");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Registration.xaml (called from another UserControl)
public partial class Registration: MetroWindow
{
public Registration()
{
InitializeComponent();
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is saved to database
// * here is where I don't know what to do, how to update the ItemSource
}
}
Here's the ComboBox's setting in UserControl.xaml:
<ComboBox x:Name="UsersCombo"
ItemsSource="{Binding List, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
Since I don't have any programming education/experience a very generic advice/explanation would be very much appreciated.
EDIT: Registration.xaml with propertychanged (still doesn't work):
public partial class Registration : MetroWindow
{
public userlist instance = new userlist();
public ListCollectionView _list1;
public ListCollectionView List1
{
get { return this._list1; }
set
{
if (this._list1 != value)
{
this._list1 = value;
this.NotifyPropertyChanged("List1");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public Registration()
{
InitializeComponent();
instance.List.PropertyChanged += ComboPropertyChangedHandler();
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is save to database
// still don't now what to do with new ListCollectionView from database
}
public void ComboPropertyChangedHandler(object obj)
{
List1 = instance.List; // when new data from database should be loaded?
}
This is where PropertyChanged event comes handy.
Bind the combobox in second xaml page to a List and create a similar property like in first xaml.
In second xaml.cs
public partial class Registration: MetroWindow, INotifyPropertyChanged
{
private userlist instance = new userlist();
private ListCollectionView _list1;
public ListCollectionView List1
{
get { return this._list1; }
set
{
if (this._list1 != value)
{
this._list1 = value;
this.NotifyPropertyChanged("List1");
}
}
}
public Registration()
{
InitializeComponent();
instance.List.PropertyChanged += ComboPropertyChangedHandler();
}
private void ComboPropertyChangedHandler(object obj)
{
List1 = instance.List;
//or iterate through the list and add as below
foreach(var item in instance.List)
{
List1.Add(item);
}
}
private void confirm_button_click(object sender, RoutedEventArgs e)
{
// new user is saved to database
// * here is where I don't know what to do, how to update the ItemSource
}
}
This is my UnityResolver Class to create the instance of IUnityContainer
public sealed class UnityResolver
{
private static IUnityContainer _unityContainer;
private static volatile UnityResolver _unityresolverinstance;
private static object syncRoot = new Object();
public static IUnityContainer UnityContainerInitiation
{
get
{
if (_unityContainer == null)
{
if (_unityresolverinstance == null)
{
lock (syncRoot)
{
if (_unityresolverinstance == null)
_unityresolverinstance = new UnityResolver();
}
}
}
return UnityResolver._unityContainer;
}
}
public UnityResolver()
{
_unityContainer = new UnityContainer();
_unityContainer.RegisterType<MaintainRouteViewModel>();
}
}
Below is my Base View and Its ViewModelCode
public partial class MaintainRouteView : UserControl
{
public MaintainRouteViewModel maintainRouteViewModel = null;
IUnityContainer container;
public MaintainRouteView()
{
InitializeComponent();
container = UnityResolver.UnityContainerInitiation;
maintainRouteViewModel = container.Resolve<MaintainRouteViewModel>();
this.DataContext = maintainRouteViewModel;
}
///This button will navigate to the child view.
private void AddRoute_Click(object sender, RoutedEventArgs e)
{
pageAnimationControl.ShowPage(new AddNewRouteView());
}
}
Its ViewModel..
public class MaintainRouteViewModel : viewModelbase
{
private string _statusSuccessMessage = null;
private string _statusFailMessage =null;
private ObservableCollection<RouteDetailsModel> _routeDetailsCollection;
public ObservableCollection<RouteDetailsModel> routeDetailsCollection
{
get
{
return this._routeDetailsCollection;
}
set
{
this._routeDetailsCollection = value;
RaisePropertyChanged("routeDetailsCollection");
}
}
public string StatusSuccessMessage
{
get
{
return _statusSuccessMessage;
}
set
{
_statusSuccessMessage = value;
this.RaisePropertyChanged("StatusSuccessMessage");
}
}
public string StatusFailMessage
{
get { return _statusFailMessage; }
set
{
_statusFailMessage = value;
this.RaisePropertyChanged("StatusFailMessage");
}
}
public MaintainRouteViewModel()
{
///it will load some data to the Observablecollection
getAllCurrentRouteData();
}
}
Now Below is my Child View and its ViewModel....
public partial class AddNewRouteView : UserControl
{
public AddNewRouteView()
{
InitializeComponent();
IUnityContainer container = UnityResolver.UnityContainerInitiation;
this.DataContext = container.Resolve<AddNewRouteViewModel>();
}
}
Its ViewModel....
public class AddNewRouteViewModel : viewModelbase
{
private MaintainRouteViewModel maintainRouteViewModel;
public ICommand SaveCommand
{
get;
set;
}
[InjectionConstructor]
public AddNewRouteViewModel(MaintainRouteViewModel maintainRouteViewModel)
{
this.maintainRouteViewModel = maintainRouteViewModel;
SaveCommand = new DelegateCommand<object>((a) => ValidateNewRoute());
}
private void ValidateNewRoute()
{
bool flag = saveAndValidate();
if(flag)
{
updateRouteStatus();
}
}
public void updateRouteStatus()
{
maintainRouteViewModel.StatusSuccessMessage = "New Route successfully Added..";
}
}
}
Can Anyone Tell me how to use this way to get the same object of MaintainRouteViewModel in my Child VM Constructor So that i will show the Updated Status Message in my Base view MaintainRouteView???
*It will Work Fine If i replace my MaintainRouteView with below code :
this Is an another approach to use IOC .i previously using this in my project. it Works Fine for me but now i want to implement the same thing using Unity Container. Please Help.
public partial class MaintainRouteView : UserControl
{
public MaintainRouteViewModel maintainRouteViewModel = null;
public MaintainRouteView()
{
InitializeComponent();
maintainRouteViewModel = new MaintainRouteViewModel();
this.DataContext = maintainRouteViewModel;
}
private void AddRoute_Click(object sender, RoutedEventArgs e)
{
pageTransitionControl.ShowPage(
new AddNewRouteView
{
DataContext = new AddNewRouteViewModel(maintainRouteViewModel)
});
}
}
I am able to solve this issue using the LifeTime Management of Unity Container Register Types.
it will work fine if i tell the container to create a singleton instance of the MaintainRouteViewModel Class.
using :
container.RegisterType<MaintainRouteViewModel>(
new ContainerControlledLifetimeManager());
But it's just a workaround to get the expected result. i want to achieve it using a proper dependency injection without any singleton instance principle. Can anyone please help to provide the solution.
I am working on a Windows 8 App using C# and Xaml as well as the MVVM-Light Toolkit.
I set everything up to create a proper binding to an ObservableCollection that gets its Data from a local database but it does not work well. It works when I edit the get property of the ObservableCollection to something like:
get
{
_Subjects.Add(new SubjectViewModel { Name = "Test" });
return _Subjects;
}
That displays the "Test"-Subject but still not the Subjects from the Database.
Nevertheless - here is all the relevant code:
The Registration in the ViewModelLocator:
public ViewModelLocator()
{
[...]
SimpleIoc.Default.Register<MainViewModel>();
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
Get the Data from a Database:
Invokation:
public sealed partial class MainPage : Stundenplaner.Common.LayoutAwarePage
{
MainViewModel mainViewModel = new MainViewModel();
[...]
protected override void OnNavigatedTo(NavigationEventArgs e)
{
mainViewModel.GetSubjects();
base.OnNavigatedTo(e);
}
}
GetSubjects method and the ObservableCollection
public MainViewModel()
{
_Subjects = new ObservableCollection<SubjectViewModel>();
}
public const string SubjectsPropertyName = "Subjects";
private ObservableCollection<SubjectViewModel> _Subjects = null;
public ObservableCollection<SubjectViewModel> Subjects
{
get
{
return _Subjects;
}
set
{
if (_Subjects == value)
{
return;
}
RaisePropertyChanging(SubjectsPropertyName);
_Subjects = value;
RaisePropertyChanged(SubjectsPropertyName);
}
}
public void GetSubjects()
{
using (var db = new SQLite.SQLiteConnection(App.DBPath))
{
var query = db.Table<Subject>().OrderBy(c => c.Name);
foreach (var _subject in query)
{
var subject = new SubjectViewModel()
{
Id = _subject.Id,
Name = _subject.Name
};
_Subjects.Add(subject);
}
}
}
The Binding to that Collection:
<GridView ItemsSource="{Binding Main.Subjects, Source={StaticResource Locator}}" [...]/>
EDIT
Thanks to Rohit Vats I've solved the problem now:
Insted of creating a new instance of the MainViewModel I've created an instance of the ViewModelLocator that accesses the registered instance of the MainViewModel like so:
ViewModelLocator Vml = new ViewModelLocator();
protected override void OnNavigatedTo(NavigationEventArgs e)
{
Vml.Main.GetSubjects();
base.OnNavigatedTo(e);
}
MainWindowViewModel instances are different that's why you database data not visible on GUI.
GridView is binded to Main -
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
ServiceLocator.Current.GetInstance<MainViewModel>(); will return new instance of MainWindowViewModel.
Whereas while navigating you are creating altogether new instance of MainWindowViewModel in MainPage and calling GetSubjects() on that instance.
You should create a single instance for MainWindowViewModel which will be shared between your View and MainPage.
I try to run some code when collection is changed. I keep collection as property in Data class:
public static ObservableCollection<OfferedConfiguration> DeviceAdjustedConfigurations
{
get { return deviceAdjustedConfigurations; }
set { deviceAdjustedConfigurations = value; }
}
and register it in code like that:
Data.DeviceAdjustedConfigurations.CollectionChanged += new NotifyCollectionChangedEventHandler(DeviceAdjustedConfigurationsCollectionChanged);
But after registration CollectionChanged is null and the appropriate code in delegated method is not run. In this place DeviceAdjustedConiguration already contains some data. What am I doing wrong?
You should avoid having a set property accessor for collection types, one reason being the one you experienced here with events. Another problem is if someone caches the collection and adds items to it later.
var old = obj.DeviceAdjustedConfigurations;
obj.DeviceAdjustedConfigurations = new ObservableCollection<OfferedConfiguration>();
old.Add(new OfferedConfiguration()); // what should happen here?
instead, remove the set-accessor and use the existing collection directly.
obj.DeviceAdjustedConfigurations.Add(new OfferedConfiguration());
If you really need to set the collection, you need to handle this with for instance a property change event from the class that owns the DeviceAdjustedConfigurations.
public class Item
{
public static ObservableCollection<OfferedConfiguration> DeviceAdjustedConfigurations
{
get { return deviceAdjustedConfigurations; }
set
{
if (deviceAdjustedConfigurations != value)
{
onDeviceConfigurationsChanging(deviceAdjustedConfigurations, value);
deviceAdjustedConfigurations = value;
}
}
}
public static event EventHandler<ConfigurationChangedEventArgs> DeviceConfigurationsChanging;
private static void onDeviceConfigurationsChanging(
ObservableCollection<OfferedConfiguration> oldList,
ObservableCollection<OfferedConfiguration> newList)
{
var handler = DeviceConfigurationsChanging;
if (handler != null)
{
handler(null, new ConfigurationChangedEventArgs(oldList, newList));
}
}
}
public class ConfigurationChangedEventArgs : EventArgs
{
public ConfigurationChangedEventArgs(
ObservableCollection<OfferedConfiguration> oldList,
ObservableCollection<OfferedConfiguration> newList)
{
OldList = oldList;
NewList = newList;
}
public ObservableCollection<OfferedConfiguration> OldList { get; private set; }
public ObservableCollection<OfferedConfiguration> NewList { get; private set; }
}
public class Consumer
{
public void foo()
{
Item.DeviceConfigurationsChanging += updateEvents;
}
private void updateEvents(object sender, ConfigurationChangedEventArgs args)
{
args.OldList.CollectionChanged -= onCollectionChanged;
args.NewList.CollectionChanged += onCollectionChanged;
}
private void onCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) { }
}