Working in a personal project learning xamarin, got stucked in opening a detailed page about a selected item which I got from an API. The API, it returns an array like this :
{
"Restaurant": [
{
"Address": "Route Bounes Aires",
"RestaurantID": "1",
"RestaurantName": "John Doe",
"City": "Lorem Ipsum"
}
{
"Address": "Route Bounes Aires",
"RestaurantID": "2",
"RestaurantName": "John Doe",
"City": "Lorem Ipsum"
}]
I managed to bind these informations in a list view using the MVVM pattern. Now I can't seem to open a detailed page for the selcted item.
This is what I have so far:
restaurantviewmodel.cs
public class RestaurantViewModel : INotifyPropertyChanged
{
Service _services = new Service();
List<Restaurant> _restaurant;
public List<Restaurant> Restaurant
{
get { return _restaurant; }
set
{
if (value == _restaurant) return;
_branches = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ICommand ReastaurantCommand
{
get
{
return new Command(async () =>
{
Reastaurant = await _apiServices.GetReastaurant();
await Application.Current.MainPage.Navigation.PushAsync(new ReastaurantPage(_restaurant));
});
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The service.cs
public async Task<List<Reastaurant>> GetReastaurant()
{
ListReastaurant restaurant = null;
try {
var client = new HttpClient();
client.DefaultRequestHeaders.Add("xxx", "xxx");
client.DefaultRequestHeaders.Add("xxx", xxx);
HttpContent content = new StringContent("");
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await client.PostAsync("https:www.here/goes/the/call/to/the/api", content);
response.EnsureSuccessStatusCode();
string json = await response.Content.ReadAsStringAsync();
restaurant = JsonConvert.DeserializeObject<ListReataurantDetails>(json);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message.ToString());
}
return restaurant.Restaurant;
}
The model restaurant.cs
public class Restaurant
{
public string Address { get; set; }
public string restaurantID { get; set; }
public string RestaurantName { get; set; }
public string City { get; set; }
}
The page restaurant.xaml :
<ListView x:Name="restaurantlistview"
HasUnevenRows="True" ItemSelected="restaurantlistview_ItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="20, 10">
<Label Text="{Binding RestaurantName}"
FontSize="20"
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
The code behind restaurant.xaml.cs
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Restaurant : ContentPage
{
public Restaurant(List<Models.Restaurant> restaurant)
{
InitializeComponent();
NavigationPage.SetTitleIcon(this, "icon.png");
restaurantlistview.ItemsSource = restaurant;
}
private async void restaurantlistview_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
await Navigation.PushAsync(new RestaurantSinglePageDetails());
}
}
How can I approach to this problem?
I want to use the details of one restaurant to another page so I can show the address and the city and use these information to do different things. I think it's pretty easy I just didn't grasp well the concept of the mvvm pattern.
To clarify I'm not trying to pass all the data to another page, but just trying to access the information about a single item(restaurant).
I would really need some help. Thanks guys!
===edit===
public partial class RestaurantSinglePageDetails: ContentPage
{
public RestaurantSinglePageDetails(Models.Restaurant res)
{
InitializeComponent();
NavigationPage.SetTitleIcon(this, "logosmall.png");
BindingContext = new RestaurantDetailsViewModel(res);
//and here I'm supposed to have access to my selected restaurant.
}
}
restaurantdetailsdviewmodel.cs
public class RestaurantDetailsViewModel : INotifyPropertyChanged
{
// ==edit==
Restaurant restaurant;
public RestaurantDetailsViewModel(Restaurant restaurant)
{
this.restaurant = restaurant; // now we can use it in ViewModel
}
Service _services = new Service();
List<Info> _info;
public List<Info> Info
{
get { return _info; }
set
{
if (value == _info) return;
_info = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ICommand GetInfoCommand
{
get
{
return new Command(async () =>
{
InfoData = await _apiServices.GetInfoData();
await Application.Current.MainPage.Navigation.PushAsync(new SingleDetailsAboutPrice(InfoData, restaurant));
});
}
}
}
I would like to use the RestaurantID here :
SingleDetailsAboutPrice.xaml.cs:
Restaurant restaurant;
public SingleDetailsAboutPrice(List<Models.InfoData> data, Restaurant restaurant)
{
InitializeComponent();
this.restaurant = restaurant;
//can't use the restaurantid here;
//some code goes here;
}
The error
The given key was not present in the dictionary
In your contentPage Restaurant class you should
InitializeComponent();
in the constructor class
XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Restaurant : ContentPage
{
public Restaurant(List<Models.Restaurant> restaurant)
{
InitializeComponent();
NavigationPage.SetTitleIcon(this, "icon.png");
restaurantlistview.ItemsSource = restaurant;
}
private async void restaurantlistview_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
//edit
var restaurant = (Restaurant)sender;
await Navigation.PushAsync(new RestaurantSinglePageDetails(restaurant));
}
}
to get item in what you selected:
var restaurant = (Restaurant)sender;
and next you have to create new Page
public partial class RestaurantSinglePageDetails: ContentPage
{
Restaurant res;
public RestaurantSinglePageDetails(Restaurant res)
{
InitializeComponent();
this.res = res;
//and here you have access to your selected restaurant.
}
}
To the res you have access from all class. So you can put this res when you move to another page.
===EDIT===
If I mean correctly, you want to pass RestaurantID to SingleDetailsAboutPrice so you have to pass it to RestaurantDetailsViewModeland then if you click on button put it to SingleDetailsAboutPrice(RestaurantId).
public partial class RestaurantSinglePageDetails: ContentPage
{
Restaurant res;
public RestaurantSinglePageDetails(Restaurant res)
{
InitializeComponent();
BindingContext = new RestaurantDetailsViewModel(item); //now you have access to restaurant in your viewModel. In this way you don't need use BindingContext in XAML
this.res = res;
//and here you have access to your selected restaurant.
}
}
And now in the RestaurantDetailsViewModel we need to create the constructor with Restaurant
public class RestaurantDetailsViewModel : INotifyPropertyChanged
{
Service _services = new Service();
Restaurant restaurant;
public RestaurantDetailsViewModel(Restaurant restaurant)
{
this.restaurant = restaurant; // now we can use it in ViewModel
}
List<Info> _info;
public List<Info> Info
{
get { return _info; }
set
{
if (value == _info) return;
_info = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
public ICommand GetInfoCommand
{
get
{
return new Command(async () =>
{
InfoData = await _apiServices.GetInfoData();
await Application.Current.MainPage.Navigation.PushAsync(new SingleDetailsAboutPrice(restaurant)); // or if you want u can pass only restaurant.restaurantID.
});
}
}
}
And in SingleDetailsAboutPrice we create constructor with Restaurant or only RestaurantId
public partial class Restaurant : ContentPage
{
Restaurant restaurant;
public SingleDetailsAboutPrice(Restaurant restaurant)
{
InitializeComponent();
this.restaurant = restaurant;
}
String restaurantID;
// if you want only restaurantID
public SingleDetailsAboutPrice(String restaurantID)
{
InitializeComponent();
this.restaurantID = restaurantID;
}
}
Related
I am using ReactiveUI in a Xamarin.Forms project but when I press a button I get the error: "Only the original thread that created a view hierarchy can touch its views. '"
Here is my code.
App.xaml.cs
public App()
{
InitializeComponent();
MainPage = new AppShell();
}
AppShell.xaml.cs
public partial class AppShell : Shell
{
Random rand = new Random();
Dictionary<string, Type> routes = new Dictionary<string, Type>();
public Dictionary<string, Type> Routes { get { return routes; } }
public AppShell()
{
InitializeComponent();
RegisterRoutes();
BindingContext = this;
}
void RegisterRoutes()
{
routes.Add("monkeydetails", typeof(HomeView));
foreach (var item in routes)
{
Routing.RegisterRoute(item.Key, item.Value);
}
}
void OnNavigating(object sender, ShellNavigatingEventArgs e)
{
// Cancel any back navigation
//if (e.Source == ShellNavigationSource.Pop)
//{
// e.Cancel();
//}
}
void OnNavigated(object sender, ShellNavigatedEventArgs e)
{
}
}
By default you go to the HomeView view
HomeView.xaml
<Button Text="Pulsar" x:Name="Boton"></Button>
HomeView.xaml.cs
public partial class HomeView : ReactiveContentPage<HomeViewModel>
{
protected CompositeDisposable ControlBindings { get; } = new CompositeDisposable();
public ReactiveCommand<Unit, Unit> Navigate { get; private set; }
public HomeView()
{
InitializeComponent();
this.ViewModel = new HomeViewModel();
this.BindCommand(ViewModel, vm => vm.Navigate, view => view.Boton);
}
}
HomeViewModel.cs
public class HomeViewModel : ViewModelBase, IRoutableViewModel
{
int prueb = 0;
public HomeViewModel()
{
Navigate = ReactiveCommand.CreateFromTask(async() =>
{
await hola();
});
}
public async Task hola()
{
}
public string prueba()
{
return prueb.ToString();
}
public IObservable<string> NumberStream { get; }
public string UrlPathSegment => "Number Stream Page";
public IScreen HostScreen { get; }
public override string Id => "Pass Parameter";
public ReactiveCommand<Unit, Unit> Navigate { get; private set; }
}
I can't understand why the error appears when I press the button
I got two pages, "HomePage", "SettingPage", including the same "MyView" (some Pickers there).
When I click "Go Setting"(or show more settings) Button from Homepage, the values syncs to the setting page. But When I click "Apply" on the setting page, the values did not come back.
I am new in c# and Xamarin and tried to search online and Microsoft docs. But I couldn't find a way to fix this issue.
Also I was following this link: How to set BindingContext of multiple pages to the same ViewModel in Xamarin.Forms?
and did the same global value in my code.
MyView (ContentView)
public MyView()
{
InitializeComponent();
BindingContext = GlobalVar.MyViewModel;
Setting1.SetBinding(Picker.ItemsSourceProperty, "ObList1");
Setting1.ItemDisplayBinding = new Binding("obj_text");
Setting1.SetBinding(Picker.SelectedItemProperty, "SelectedItem1");
//also other pickers
}
HomePage (including the MyView)
public SearchPage ()
{
InitializeComponent ();
BindingContext = GlobalVar.MyViewModel;
}
private async void Click_GoSetting(object sender, EventArgs e)
{
await Navigation.PushAsync(new SettingPage());
}
SettingPage (including the same MyView)
public partial class SettingPage : ContentPage
{
MyViewModel viewModel { get; set; } = GlobalVar.MyViewModel;
public SettingPage ()
{
BindingContext = viewModel;
}
private async void Click_ApplySetting(object sender, EventArgs e)
{
await Navigation.PopAsync(true);
}
//some other method deal with viewModel
}
GLobalVar.cs
private static MyViewModel _myViewModel = new MyrViewModel();
public static MyViewModel MyViewModel
{
get
{
return _myViewModel;
}
}
ViewModel
public class MyViewModel : BaseViewModel
{
public ObservableCollection<obj> ObList1 { get; set; }
public ObservableCollection<obj> ObList2 { get; set; }
public ObservableCollection<obj> ObList3 { get; set; }
public obj SelectedItem1 { get; set; }
public obj SelectedItem2 { get; set; }
public obj SelectedItem3 { get; set; }
public MyViewModel()
{
ObList1 = new ObservableCollection<obj>();
ObList2 = new ObservableCollection<obj>();
ObList3 = new ObservableCollection<obj>();
}
}
Maybe I should notify the changes on my SettingPage to viewmodel? or do something in the "set" in viewmodel?
The confusing point is that two pages embed the same view using the same viewmodel, but notify the change from Page1 to Page2 only, not Page2 to Page1.
Any ideas, thx in advance.
Solution One:
Using Event can pass value back to Previous Page.
Define Event in SecondPage :
public delegate void EventHandler(string status);
public event EventHandler EventPass;
Invoke Event when Page disappear:
protected override void OnDisappearing()
{
base.OnDisappearing();
EventPass("Back Code");
}
In FirstPage, when Naviagtion place need to add the Event here:
string title = "PageSecondParamater";
PageSecond pageSecond = new PageSecond(title);
pageSecond.EventPass += PageSecond_EventPass; ;
Navigation.PushAsync(pageSecond);
Now value will be passed here:
private void PageSecond_EventPass(string status)
{
Title = status;
Console.WriteLine("---" + status);
}
Solution Two:
Using Properties Dictionary to store easy and small size data in Application, when enter in page will invoke it to get data from which has been stored.
In Second Page Where you want to store data, writing as bellow:
Application.Current.Properties ["value"] = valuedata;
When back to First Page, override OnAppearing method to update UI:
protected override void OnAppearing()
{
base.OnAppearing();
if (Application.Current.Properties.ContainsKey("value"))
{
var ValueGet = Application.Current.Properties ["value"] as DataType;
// do something with other things
}
}
Note: ViewModel if want to dynamic update data , need to use INotifyPropertyChanged .
Sample Implementation:
public class ObservableProperty : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
ViewModelBase suggest implementing ICommand as a Dictionary structure like:
public abstract class ViewModelBase : ObservableProperty
{
public Dictionary<string,ICommand> Commands { get; protected set; }
public ViewModelBase()
{
Commands = new Dictionary<string,ICommand>();
}
}
So all todo in your ViewModel is just inherit the ViewModelBase class and use it:
class LoginViewModel : ViewModelBase
{
string userName;
string password;
public string UserName
{
get {return userName;}
set
{
userName = value;
OnPropertyChanged("UserName");
}
}
public string Password
{
get{return password;}
set
{
password = value;
OnPropertyChanged("Password");
}
}
#endregion
#region ctor
public LoginViewModel()
{
//Add Commands
Commands.Add("Login", new Command(CmdLogin));
}
#endregion
#region UI methods
private void CmdLogin()
{
// do your login jobs here
}
#endregion
}
Solved.
MyViewModel (updated)
public class MyViewModel : BaseViewModel
{
public ObservableCollection<obj> ObList1 { get; set; }
public ObservableCollection<obj> ObList2 { get; set; }
public ObservableCollection<obj> ObList3 { get; set; }
private obj _selectedItem1 = new obj();
public obj SelectedItem1
{
get { return _selectedItem1; }
//this is the line solved the problem
//but still not understood thoroughly
set { SetProperty(ref _selectedItem1, value); }
}
//same for _selectedItem2 _selectedItem3
}
ps: BaseViewModel codes here (not changed, from template codes)
public class BaseViewModel : INotifyPropertyChanged
{
//some other attributes
//...
protected bool SetProperty<T>(ref T backingStore, T value,
[CallerMemberName]string propertyName = "",
Action onChanged = null)
{
if (EqualityComparer<T>.Default.Equals(backingStore, value))
return false;
backingStore = value;
onChanged?.Invoke();
OnPropertyChanged(propertyName);
return true;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var changed = PropertyChanged;
if (changed == null)
return;
changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
It seems that by calling SetProperty, OnPropertyChanged will also be revoked.
But still a little bit confusing about why the previous codes go like kind of "one-way" binding.
I'm using MVVM with WPF and have a RadComboBox in my view that needs to be populated from my County table in my database. My viewmodel is as follows:
public class AddClientViewModel : BindableBase
{
private Client _client;
private Circuit _circuit;
private County _county;
private State _state;
private SubscriberSpecialty _subscriberSpecialty;
private IClientsRepository _repository = new ClientRepository();
private ICircuitRepository _circuitRepository = new CircuitRepository();
private ICountyRepository _countyRepository = new CountyRepository();
private IStateRepository _stateRepository = new StateRepository();
private ISubscriberSpecialty _subscriberSpecialtyRepository = new SubscriberSpecialtyRepository();
public AddClientViewModel()
{
SaveCommand = new RelayCommand(OnSave);
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public Client Client
{
get { return _client; }
set
{
if (value != _client)
{
_client = value;
PropertyChanged(this, new PropertyChangedEventArgs("Client"));
}
}
}
public Circuit Circuit
{
get { return _circuit; }
set
{
if(value != _circuit)
{
_circuit = value;
PropertyChanged(this, new PropertyChangedEventArgs("Circuit"));
}
}
}
public County County
{
get { return _county;}
set
{
if (value != _county)
{
_county = value;
PropertyChanged(this, new PropertyChangedEventArgs("County"));
}
}
}
public State State
{
get { return _state; }
set
{
if (value != _state)
{
_state = value;
PropertyChanged(this, new PropertyChangedEventArgs("State"));
}
}
}
public SubscriberSpecialty SubscriberSpecialty
{
get { return _subscriberSpecialty; }
set
{
if (value != _subscriberSpecialty)
{
_subscriberSpecialty = value;
PropertyChanged(this, new PropertyChangedEventArgs("SubscriberSpecialty"));
}
}
}
public Guid ClientId { get; set; }
public Guid CircuitId { get; set; }
public Guid CountyId { get; set; }
public Guid StateId { get; set; }
public Guid SubscriberSpecialtyId { get; set; }
public ICommand SaveCommand { get; set; }
public event Action<Client> AddClient = delegate { };
public async void LoadClient()
{
Client = await _repository.GetClientAsync(ClientId);
}
public async void LoadCircuit()
{
Circuit = await _circuitRepository.GetCircuitAsync(CircuitId);
}
public async void LoadCounty()
{
County = await _countyRepository.GetCountyAsync(CountyId);
}
public async void LoadState()
{
State = await _stateRepository.GetStateAsync(StateId);
}
public async void LoadSubscriberSpecialty()
{
SubscriberSpecialty = await _subscriberSpecialtyRepository.GetSubscriberSpecialtyAsync(SubscriberSpecialtyId);
}
private void OnAddClient()
{
AddClient(new Client {ClientId = Guid.NewGuid()});
}
private async void OnSave()
{
try
{
Client = await _repository.AddClientAsync(new Client());
}
catch (Exception ex)
{
MessageBox.Show("A handled exception just occurred: " + ex.Message, "Exception", MessageBoxButton.OK,
MessageBoxImage.Warning);
}
}
}
The interface has the following:
Task<County> GetCountyAsync(Guid countyId);
The repository class calls the interface as:
public Task<List<County>> GetCountiesAsync()
{
return _context.Counties.ToListAsync();
}
My view then uses the following syntax:
<telerik:RadComboBox x:Name="Countycombo"
Grid.Column="1" Grid.Row="3"
ItemsSource="{Binding County.CountyName}"
DisplayMemberPath="CountyName" Width="120"/>
I defined a DataContext in the layout as follows:
<UserControl.DataContext>
<viewModels:AddClientViewModel />
</UserControl.DataContext>
When I run the application, the RadComboBox doesn't grab the values from the County table, into which I've loaded several values for CountyName. How do I correct the above code snippets to ensure my County Names are populated?
Update: When I remove County from County.CountyName, I receive the message stating Cannot resolve property CountyName in DataContext MySolution.ViewModels.MyViewModel What additional work is needed in the viewmodel either in LoadCounty or other sections?
I would suggest the following:
Introduce the ViewModel property that will hold a list of County objects:
private List<County> _counties;
public List<County> Counties
{
get { return _counties;}
set
{
if (value != _counties)
{
_counties = value;
PropertyChanged(this, new PropertyChangedEventArgs("Counties"));
}
}
}
Bind a ComboBox ItemsSource to the Counties property, and a ComboBox SelectedItem property to the County property.
<telerik:RadComboBox x:Name="Countycombo"
Grid.Column="1" Grid.Row="3"
ItemsSource="{Binding Counties}"
SelectedItem="{Binding County}"
DisplayMemberPath="CountyName" Width="120"/>
And you need to a place where you will load the counties with a repository call to a GetCountiesAsync. The result should be set to the ViewModel Counties property.
public async void LoadCounties()
{
Counties = await _countyRepository.GetCountiesAsync();
}
Not sure what is the best place to make that call.
I have two bound textboxes in my View.
<TextBox Text="{Binding BookingWizard.CustomerView.Email,Mode=TwoWay}" />
<TextBox Text="{Binding BookingWizard.CustomerView.ContactNo,Mode=TwoWay}" />
I can populate these fields when another textbox has lost its focus. the code behind for that bit is:
private void txtFirstName_LostFocus(object sender, RoutedEventArgs e)
{
LookUpEmailAndContactNo();
}
private void LookUpEmailAndContactNo()
{
var vm = this.DataContext as ApplicationViewModel;
var customer = vm.BookingWizard.LookUpEmailAndContactNo();
//etc
vm.BookingWizard.CustomerView.Email = customer.Email;
}
public Customer LookUpEmailAndContactNo()
{
var res= InformedWorkerBusinessService.Customer.GetEmailAndContactNo(CustomerView.FName, CustomerView.SName);
if (res!=null)
{
CustomerView.Email = res.Email;
CustomerView.ContactNo = res.ContactNo;
}
return CustomerView;
}
This is the screenshot of my data context when i set a break-point in the LookUpEmailAndContactNo event:
As you can see the data context does have these values but I cannot see what is wrong with my UI binding?
ADDITIONAL:
I set my view model at the App entry point:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ApplicationView app = new ApplicationView();
ApplicationViewModel context = new ApplicationViewModel();
context.ActiveRecord = new ActiveRecord();
context.CustomerSearch = new CustomerSearch();
context.BookingWizard = new BookingWizard();
context.BookingWizard.CustomerView = new InformedWorkerModel.Customer();
context.BookingWizard.JobView = new InformedWorkerModel.Job();
app.DataContext = context;
app.Show();
}
}
This is inside my BookingWizard class:
public class BookingWizard : ViewModelBase, IDataErrorInfo
{
Customer _Customer;
public bool IsExistingCustomer { get; set; }
public IEnumerable<string> FNames
{
get
{
if (CustomerView.SName == null)
{
CustomerView.SName = string.Empty;
}
return InformedWorkerBusinessService.Customer.GetFirstNames(CustomerView.SName);
}
}
public Customer LookUpEmailAndContactNo()
{
var res= InformedWorkerBusinessService.Customer.GetEmailAndContactNo(CustomerView.FName, CustomerView.SName);
if (res!=null)
{
CustomerView.Email = res.Email;
CustomerView.ContactNo = res.ContactNo;
}
return CustomerView;
}
public Customer CustomerView
{
get { return _Customer; }
set
{
_Customer = value; RaisePropertyChanged("CustomerView");
}
}
}
and in my Customer Class:
[Table("Customer")]
public class Customer
{
[AutoIncrement]
[PrimaryKey]
public int CustomerId { get; set; }
public string SName { get; set; }
public string FName { get; set; }
public string ContactNo { get; set; }
public string Email { get ; set; }
}
Update
Managed to fix the selectedIndex problem. I'd forgotten to set SelectedItem as well and naturally that caused a few issues.
So at 9AM this morning we got our 24 hour assignment and I have hit a brick wall.
We're supposed to create a program that allows a supervisor to Add and delete Employees and add Working Sessions, total hours and total earnings. But I am having some problems succesfully implementing this following the MVVM-Pattern. For some reason my Bindings simply aren't working and the only Solution I can see is someone looking over my project and helping me troubleshoot it.
Here is my code - I'm very sorry about having to post the entire thing but given that I have no clue where the problem is I did not see any other options. :
EmployeeModel
[Serializable]
public class WorkSessions : ObservableCollection<WorkSessionModel>
{
public WorkSessions()
{
}
}
[Serializable]
public class WorkSessionModel : INotifyPropertyChanged
{
private DateTime _dateTime;
private string _id;
private double _hours;
public WorkSessionModel()
{
}
public DateTime DateTime
{
get { return _dateTime; }
set
{
_dateTime = value;
NotifyPropertyChanged("DateTime");
}
}
public string ID
{
get { return _id; }
set
{
_id = value;
NotifyPropertyChanged("ID");
}
}
public double Hours
{
get { return _hours; }
set
{
_hours = value;
NotifyPropertyChanged("Hours");
NotifyPropertyChanged("TotalHours");
}
}
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
WorkSessionModel
[Serializable]
public class WorkSessions : ObservableCollection<WorkSessionModel>
{
public WorkSessions()
{
}
}
[Serializable]
public class WorkSessionModel : INotifyPropertyChanged
{
private DateTime _dateTime;
private string _id;
private double _hours;
public WorkSessionModel()
{
}
public DateTime DateTime
{
get { return _dateTime; }
set
{
_dateTime = value;
NotifyPropertyChanged("DateTime");
}
}
public string ID
{
get { return _id; }
set
{
_id = value;
NotifyPropertyChanged("ID");
}
}
public double Hours
{
get { return _hours; }
set
{
_hours = value;
NotifyPropertyChanged("Hours");
NotifyPropertyChanged("TotalHours");
}
}
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
EmployeeViewModel
public class EmployeeViewModel : ViewModelBase
{
private Employees _employeesModel = new Employees();
public Employees EmployeesView = new Employees();
public ObservableCollection<WorkSessionModel> WorkSessions { get; set; }
private string _id = "0";
private string _name = "noname";
private double _wage = 0;
private int _totalhours = 0;
public string ID
{
get { return _id; }
set { _id = value; RaisePropertyChanged("ID"); }
}
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
public double Wage
{
get { return _wage; }
set
{
_wage = value;
RaisePropertyChanged("Wage");
}
}
public int TotalHours
{
get { return _totalhours; }
set
{
_totalhours = value;
RaisePropertyChanged("TotalHours");
}
}
private EmployeeModel _selectedEmployee = new EmployeeModel();
public EmployeeModel SelectedEmployee
{
get { return _selectedEmployee; }
set
{
_selectedEmployee = value;
RaisePropertyChanged("SelectedEmployee");
}
}
private int _selectedEmployeeIndex;
public int SelectedEmployeeIndex
{
get { return _selectedEmployeeIndex; }
set
{
_selectedEmployeeIndex = value;
RaisePropertyChanged("SelectedEmployeeIndex");
}
}
#region RelayCommands
// Employee Relay Commands
public RelayCommand EmployeeAddNewCommand { set; get; }
public RelayCommand EmployeeDeleteCommand { set; get; }
public RelayCommand EmployeeNextCommand { set; get; }
public RelayCommand EmployeePrevCommand { set; get; }
public RelayCommand EmployeeTotalHoursCommand { get; set; }
#endregion
public EmployeeViewModel()
{
InitCommands();
}
private void InitCommands()
{
EmployeeAddNewCommand = new RelayCommand(EmployeeAddNewExecute, EmployeeAddNewCanExecute);
EmployeeDeleteCommand = new RelayCommand(EmployeeDeleteNewExecute, EmployeeDeleteCanExecute);
EmployeeNextCommand = new RelayCommand(EmployeeNextExecute, EmployeeNextCanExecute);
EmployeePrevCommand = new RelayCommand(EmployeePrevExecute, EmployeePrevCanExecute);
//EmployeeTotalHoursCommand = new RelayCommand(EmployeeTotalHoursExecute, EmployeeTotalHoursCanExecute);
}
//private void EmployeeTotalHoursExecute()
//{
// _selectedEmployee.TotalHours();
//}
//private bool EmployeeTotalHoursCanExecute()
//{
// return true;
//}
private void EmployeeAddNewExecute()
{
EmployeeModel newEmployee = new EmployeeModel();
EmployeesView.Add(newEmployee);
_employeesModel.Add(newEmployee);
SelectedEmployee = newEmployee;
}
private bool EmployeeAddNewCanExecute()
{
return true;
}
private void EmployeeDeleteNewExecute()
{
if (MessageBox.Show("You are about delete all submissions for Employee," + SelectedEmployee.Name + "(" + SelectedEmployee.ID +")\r\nAre you sure?", "This is a Warning!", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
_employeesModel.Remove(SelectedEmployee);
EmployeesView.Remove(SelectedEmployee);
}
}
private bool EmployeeDeleteCanExecute()
{
if (SelectedEmployee != null)
return true;
else return false;
}
private void EmployeeNextExecute()
{
SelectedEmployeeIndex++;
}
private bool EmployeeNextCanExecute()
{
if (SelectedEmployeeIndex < EmployeesView.Count - 1)
return true;
return false;
}
private void EmployeePrevExecute()
{
SelectedEmployeeIndex--;
}
private bool EmployeePrevCanExecute()
{
if (SelectedEmployeeIndex > 0)
return true;
return false;
}
}
View
public partial class MainWindow : Window
{
public EmployeeViewModel EmployeeViewModel = new EmployeeViewModel();
public MainWindow()
{
InitializeComponent();
menu_employee.DataContext = EmployeeViewModel;
sp_employees.DataContext = EmployeeViewModel;
datagrid_employees.ItemsSource = EmployeeViewModel.EmployeesView;
grid_selectedEmployee.DataContext = EmployeeViewModel.SelectedEmployee;
}
}
I can see a few problems with your code:
When the SelectedIndex is updated, SelectedItem remains the same and vice versa.
It looks like your data binding is out of order:
The DataContext property cascades down to every child of a certain dependency object.
The code in the MainWindow constructor should probably be replaced by:
this.DataContext = EmployeeViewModel;
Then in XAML set the rest of the properties using Data Binding. The problem in your situation is that the DataContext of the selectedemployee is only set once. This means if you select another employee, then it will not update.
An example for your SelectedEmployee grid:
<Grid Name="grid_selectedEmployee" DataContext="{Binding SelectedEmployee,
UpdateSourceTrigger=PropertyChanged}">...</Grid>
One of the biggest things I see is you are setting properties, not binding them.
For example,
datagrid_employees.ItemsSource = EmployeeViewModel.EmployeesView;
You are telling your DataGrid that it's ItemsSource should be that specific object. You need to bind it to that value so you are telling it to point to that property instead. This will make your UI correctly reflect what's in your ViewModel
The other huge red flag I see is your ViewModel referencing something called and EmployeeView which leads me to believe your View and ViewModel too closely tied together.
Your ViewModel should contain all your business logic and code, while the View is usually XAML and simply reflects the ViewModel in a user-friendly way.
The View and the ViewModel should never directly reference each other (I have had my View reference my ViewModel in some rare occasions, but never the other way around)
For example, an EmployeesViewModel might contain
ObservableCollection<Employee> Employees
Employee SelectedEmployee
ICommand AddEmployeeCommand
ICommand DeleteEmployeeCommand
while your View (XAML) might look like this:
<StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="Add" Command="{Binding AddEmployeeCommand}" />
<Button Content="Delete" Command="{Binding DeleteEmployeeCommand}" />
</StackPanel>
<DataGrid ItemsSource="{Binding Employees}"
SelectedItem="{Binding SelectedEmployee}">
... etc
</DataGrid>
<UniformGrid DataContext="{Binding SelectedEmployee}" Columns="2" Rows="4">
<TextBlock Text="ID" />
<TextBox Text="{Binding Id}" />
... etc
</UniformGrid >
</StackPanel>
And the only thing you should be setting is the DataContext of the entire Window. Usually I overwrite App.OnStartup() to start up my application:
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var view = new MainWindow();
var vm = new EmployeesViewModel;
view.DataContext = vm;
view.Show();
}
}
Although I suppose in your case this would also work:
public MainWindow()
{
InitializeComponent();
this.DataContext = new EmployeesViewModel();
}