I'm using WPF to create an application to enable an organisation to enter different pieces of data into the application.I have a tab control to allow them to do this.
Then in a separate view, I have a series of different data grids showing the user what data they have inserted into the database. Containing buttons to either, add, update or delete the data they want.
Which leads me to my question. Currently, I am able to delete, and add data with ease and with no problem. But then comes my issue with trying to get the selected item to update, which it doesn't, resulting in a null reference exception.
If i set my property attributes programmatically though, it updates it fine. like so;public int _OrganisationTypeDetailID = 17; public int _OrganisationTypeID = 1;But I do not want this, as I want the ability for the user to select for themselves and update the data they need to.
Here's some of the code that may help in resolving my issue;
View Model;
public void UpdateOrganisationTypeDetail(OrganisationTypeDetail orgTypeDetail)
{
using (DBEntities context = new DBEntities())
{
var orgTD = context.OrganisationTypeDetails.Where(otd => otd.OrganisationTypeDetailID == SelectedType.OrganisationTypeDetailID).FirstOrDefault();
if (orgTD != null)
{
orgTD.Title = Title;
orgTD.FirstName = FirstName;
orgTD.Surname = Surname;
orgTD.Position = Position;
orgTD.DateOfBirth = DateOfBirth;
orgTD.Address = Address;
orgTD.Country = Country;
orgTD.Postcode = Postcode;
orgTD.PhoneNumber = PhoneNumber;
orgTD.MobileNumber = MobileNumber;
orgTD.FaxNumber = FaxNumber;
orgTD.Email = Email;
orgTD.NINumber = NINumber;
//context.OrganisationTypeDetails.Attach(orgTD);
context.OrganisationTypeDetails.ApplyCurrentValues(orgTD);
context.SaveChanges();
MessageBox.Show("Updated Organisation Type Details");
}
else
{
MessageBox.Show("Unable to update selected 'Type'.");
}
}
private OrganisationTypeDetail _SelectedType;
public OrganisationTypeDetail SelectedType
{
get
{
return _SelectedType;
}
set
{
if (_SelectedType == value)
return;
_SelectedType = value;
OnPropertyChanged("SelectedType");
}
}
public List<OrganisationTypeDetail> GetOrganisationTypeDetail //Loads data
{
get
{
using (DBEntities context = new DBEntities())
{
var query = from e in context.OrganisationTypeDetails
select e;
return query.ToList<OrganisationTypeDetail>();
}
}
}
private ICommand showUpdateCommand;
public ICommand ShowUpdateCommand //Update command
{
get
{
if (showUpdateCommand == null)
{
showUpdateCommand = new RelayCommand(this.UpdateFormExecute, this.UpdateFormCanExecute); //i => this.UpdateOrganisationTypeDetail()
}
return showUpdateCommand;
}
}
Code behind;
private void btnUpdateOrgTypeDetail_Click(object sender, RoutedEventArgs e)
{
OrganisationTypeDetail selected = dgOrgTypeDetail.SelectedItem as OrganisationTypeDetail;
OrganisationTypeDetailViewModel org = new OrganisationTypeDetailViewModel();
if (selected == null)
MessageBox.Show("You must select a 'Type' before updating.");
else
{
OrganisationTypeDetailUpdateView update = new OrganisationTypeDetailUpdateView();
update.ShowDialog();
org.UpdateOrganisationTypeDetail(selected);
Page_Loaded(null, null);
}
}
xaml;
<DataGrid Name="dgOrgTypeDetail" Height="145" Width="555"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding GetOrganisationTypeDetail}"
SelectedItem="{Binding SelectedType, Mode=TwoWay}">
Hope this issue can be resolved.
I would say that your best bet for this is to use commanding in the MVVM pattern to achieve this..
It looks like you're using a combination of MVVM and code behind and actually creating a new instance of the view model when your click event fires. Try binding the view model to your view once in the code behind of the view as the datacontext and then try updating the selected type..
Also when you're trying to do the update on SelectedType - look at your View using Snoop - see if the SelectedType property is still bound to the view.
ICommand UpdateOrgTypeDetail { get;}
Then in the view model constructor declare new instance
UpdateOrgTypeDetail = new DelegateCommand<object>(ExecuteUpdateOrgTypeDetail, CanExecuteUpdateOrgTypeDetail);
These two delegates will then allow you to click your button (which needs to bind to UpdateOrgTypeDetail)
<Button Command="{Binding UpdateOrgTypeDetail}" />
You should find that the update on the property is done correctly from here.
Related
I'm working on WPF application, and I have users, and ofcourse users has some kind of roles, in my case SUPERADMIN AND ADMIN, that roles are stored in table "Roles", One user could have 1 or more roles, so that means one or more checkbox can be selected on my form checkboxes that I generated dynamically on this way:
private void LoadRolesToStackPanel()
{
try
{
var roles = RolesController.Instance.SelectAll();
if (roles.Count > 0)
{
foreach (Role r in roles)
{
CheckBox cb = new CheckBox();
//cb.Name = r.RoleId.ToString();
cb.Content = r.Title.ToString();
cb.FontSize = 15;
stackRole.Children.Add(cb);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Later when I'm saving User I need to get ID from checked combobox because it is representing Role acctually, so I need to make insert to database with corresponding RoleId..
Here is how I took selected checkboxes on event SaveUser:
IEnumerable<CheckBox> selectedBoxes =
from checkbox in this.stackRole.Children.OfType<CheckBox>()
where checkbox.IsChecked.Value
select checkbox;
foreach (CheckBox box in selectedBoxes)
{
// do something
}
As it is possible to notice I can get Title from combobox because I said
cb.Content = r.Title.ToString();
But I need ID of "r" - role also, and I don't know how to add it to a checkbox on way I added Title.
The "correct" way of solving this would be to use an ItemsControl that you bind to an IEnumerable<Role>:
<ItemsControl ItemsSource="{Binding Roles}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Title}" FontSize="15" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
public partial class MainWindow : Window
{
private readonly ViewModel _viewModel = new ViewModel();
public MainWindow()
{
InitializeComponent();
DataContext = _viewModel;
}
}
public class ViewModel
{
public ViewModel()
{
Roles = RolesController.Instance.SelectAll();
}
public List<Role> Roles { get; private set; }
}
This means that you should add an IsChecked property to the Role class (or create a new client application specific Role class that has this property and use this one instead of your current Role class).
You could then iterate through the selected roles, either in the code-behind of the view or in the view model:
foreach (var role in _viewModel.Roles)
{
if(role.IsSelected)
{
//...
}
}
This solution is built on MVVM which is the recommended design pattern to use when developing XAML based user interface applications. You should learn it. You could read more about it here: https://msdn.microsoft.com/en-us/library/hh848246.aspx.
Because noone answered on this question for like 1 hour, I think it's ok to post answer by myself because I've found some kind of solution, I don't know is it good but it works, well for selected Check Boxes because they are acctually generated from Database (Roles) it is possible to do something like this:
private void LoadRolesToStackPanel()
{
try
{
var roles = RolesController.Instance.SelectAll();
if (roles.Count > 0)
{
foreach (Role r in roles)
{
CheckBox cb = new CheckBox();
cb.Tag = r;
cb.Content = r.Title.ToString();
cb.FontSize = 15;
stackRole.Children.Add(cb);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
foreach (CheckBox box in selectedBoxes)
{
Role r = (Role)box.Tag;
//Here we get Id and every other Property that Role had
var x = r.Id;
}
So I included .Tag property and assigned real object from DB to that tag, so I could later easily access to a properties of that DB object from my selected checkbox!
First time I see this guy "Tag" but it is awesome, and I would please anyone with bigger experience with "Tag" to explain to us why does this works like this, and how come it's so easy to access object with .Tag propery.
I have a DataGrid with bound ItemsSource:
<DataGrid
x:Name="grid"
FlowDirection="RightToLeft"
AutoGenerateColumns="False"
ItemsSource="{Binding ListGuards}"
IsReadOnly="True">
Columns definition (One example):
<DataGridTextColumn Header="ID" Binding="{Binding Id}" />
Code-behind:
public ObservableCollection<TableGuard> ListGuards { get; set; }
public C'tor(...) {
ListGuards = new ObservableCollection<TableGuard>(s); //s = List<TableGuard>
this.DataContext = this;
}
Submitting data:
private void submit(object sender, RoutedEventArgs e) {
if (list == null)
list = new List<TableGuard>();
TableGuard guard = new TableGuard();
guard.FName = fName.Text;
guard.LName = lName.Text;
guard.Id = int.Parse(id.Text);
guard.WorkPlace = workPlace.Text;
guard.LastTraining = lastTraining.Text;
guard.NextTraining = nextTraining.Text;
guard.Birthday = birthDay.Text;
guard.TrainingType = trainingType.Text;
bool exists = false;
foreach (var g in list)
if (guard.Id == g.Id) {
exists = true;
break;
}
if (exists)
MessageBox.Show("ID EXISTS!");
else if (id.Text.Length < 9)
MessageBox.Show("ID NEEDS TO BE 9 DIGITS!");
else {
if (isEdit) {
jsonObj[selectedRow]["Id"] = guard.Id;
jsonObj[selectedRow]["FName"] = guard.FName;
jsonObj[selectedRow]["LName"] = guard.LName;
jsonObj[selectedRow]["Birthday"] = guard.Birthday;
jsonObj[selectedRow]["LastTraining"] = guard.LastTraining;
jsonObj[selectedRow]["NextTraining"] = guard.NextTraining;
jsonObj[selectedRow]["WorkPlace"] = guard.WorkPlace;
jsonObj[selectedRow]["TrainingType"] = guard.TrainingType;
File.WriteAllText(path, JsonConvert.SerializeObject(jsonObj));
Close();
} else {
list.Add(guard);
File.WriteAllText(path, JsonConvert.SerializeObject(list));
Close();
}
}
}
I wish to refresh the DataGrid after I edit some data. How do I do it?
try this:
grid.Items.Refresh();
I do wonder if this will work for your situation.
edit:spelling
your TableGuard class has to implement INotifyPropertyChanged and raise it properly. And in your columnsdefinition of your datagrid you have to set the mode to TwoWay. if you add and remove items in your collection look at the other answer.
just for the downvote:
if you debug your code and in the row in your ctor
this.DataContext = this;
your Property ListGuards property is empty and you never use ListGuards.Add/Remove you should not see anything in your grid.
Android
in your Submitted method you either set ListGuard to list or simply add all the objects in ListGuard collection
As you are using an ObservableCollection, your grid will update when an item is added or removed from the list.
However, if you are instantiating a new ObservableCollection, then your grid will not update, as your property does not implement INotifyPropertyChanged.
Implement the INotifyPropertyChanged interface in your class and call the PropertyChanged event to update your grid:
private ObservableCollection<TableGuard> _ListGuards;
public ObservableCollection<TableGuard> ListGuards
{
get { return _ListGuards; }
set
{
_ListGuards = value;
OnPropertyChanged("ListGuards");
}
}
EDIT:
At the end of your submit method, you simply need to reset all of the items in the ListGuards list. Either call the .Clear() method and add the items to the collection or instantiate a new ObservableCollection after implementing INotifyPropertyChanged on the property.
I started implementing a MVVM design pattern in an existing WPF c# application. I am completely new and have never used design patterns or dependency injection before. I was looking at the frameworks already available and have adopted MVVM light. I moved the logic from the view to the viewmodel. I have lot of code in the PopulateTestMenu which is related to UI in the view model. It also has calls to the event handlers. How do I take care of this?
In the XAML I have:
<Window DataContext="{Binding Main, Source={StaticResource Locator}}">
<Menu>
<MenuItem Header="Load All History..." Command="{Binding LoadAllHistory}">
In the MainViewModel class I have:
public ICommand LoadAllHistory { get; private set; }
public MainViewModel()
{
LoadAllHistory = new RelayCommand(() => LoadHistoryExecute(), () => true);
}
The code that I moved from my view to the viewmodel:
private void LoadHistoryExecute()
{
try
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "Test History File (*.xml)|*.xml";
ofd.Title = "Open Test History";
ofd.Multiselect = true;
if (ofd.ShowDialog() == true)
{
ThreadPool.QueueUserWorkItem(LoadTestHistoryCallback, ofd.FileNames);
}
}
catch
{
//some code
}
}
private void LoadTestHistoryCallback(object state)
{
try
{
string[] fileNames = (string[])state;
foreach (string fileName in fileNames)
{
bool success = MyApp.Instance.ParseTestHistory(fileName);
string status = success
? String.Format("'{0}' loaded successfully.",
System.IO.Path.GetFileName(fileName))
: String.Format("Failed to load history from '{0}'.",
System.IO.Path.GetFileName(fileName));
Dispatcher.CurrentDispatcher.DynamicInvoke(delegate()
{
Status = status;
});
PopulateTestMenu(new SortedList<int, int>());
}
}
catch
{
//some code
}
}
private void PopulateTestMenu(SortedList<int, int> indexes)
{
try
{
_testMenuMutex.WaitOne();
//Populate the Tests menu with the list of tests.
Dispatcher.CurrentDispatcher.DynamicInvoke(delegate()
{
menuTests.Items.Clear();
var checkEventHandler = new RoutedEventHandler(testMenuItem_Checked);
bool added = false;
if (MyApp.Instance.TestHistory != null &&
MyApp.Instance.TestHistory.Count > 0)
{
List<ushort> subIds = new
List<ushort>MyApp.Instance.TestHistory.Keys);
foreach (ushort subId in subIds)
{
MenuItem menuItem = null;
menuItem = new MenuItem();
menuItem.Header = subId.ToString().PadLeft(5, '0');**
MenuItem none = new MenuItem();
none.Header = "None";
none.IsCheckable = true;
none.IsChecked = true;
none.Checked += checkEventHandler;
none.Unchecked += checkEventHandler;
menuItem.Items.Add(none);
if (MyApp.Instance.TestHistory != null &&
MyApp.Instance.TestHistory.ContainsKey(subId))
{
var tests = MyApp.Instance.TestHistory[subId];
if (tests != null)
{
foreach (Test t in tests)
{
MenuItem item = new MenuItem();
item.IsCheckable = true;
string description = t.Description.Replace("\n",
"\n".PadRight(34, ' '));
string header = abc;
item.Header = header;
item.DataContext = t;
item.Checked += checkEventHandler;
item.Unchecked += checkEventHandler;
menuItem.Items.Add(item);
}
if (tests.Count > 0)
{
menuTests.Items.Add(menuItem);
added = true;
}
}
}
// Carry over the previous selection.
if (indexes.ContainsKey(subId) && indexes[subId] > -1)
{ ((MenuItem)menuItem.Items[indexes[subId]]).IsChecked =
true;
}
}
}
I am still trying to figure out what you are asking =)...
But you are mixing up some things... Remember one of the core concepts of MVVM is to make the viewmodel testable and remove all view related code off from the viewmodel. So no dependencies to WPF at all. So MenuItem looks like a WPF MenuItem and should not be in your ViewModel.
Instead you could consider to make a MenuItemViewModel which binds to the MenuItem in the View. And it I could see an ObservableCollection<MenuItemViewModel> TestMenu instead of your sorted list.
In your method LoadTestHistoryCallback you would instanciate (could be done via DI) the MenuItemViewModel and add it to the TestMenu Collection. The MenuItemViewModel could have status property which could be assigned from outside or internaly. (It can also have some additional logic, hey its a viewmodel).
In the View you could then bind it to a list with a template representing the MenuItem via DataBinding.
<Menu DockPanel.Dock="Top" ItemsSource="{Binding Path=MenuItems}" />
So remember ViewModel can also contain ViewModels or collections of viewmodel.
Use the rich databinding api from WPF.
Work with bindable Properties like ObservebaleCollections or Properties that are extended with PropertyChanged notification.
HTH
P.S: You can then have a click ICommand in the MenuItemViewModel and execute actions or better use the EventAggregator or Messenger to notify other ViewModels ...(but that's a story for another question =)... )
You have applied the theory of MVVM correctly by moving that code to the ViewModel however just keep in mind that the View should only provide the "structure" of the display.
What is displayed is provided by the model in the ViewModel.
With that in mind separate out the menu parts from the ViewModel method and put them in the View, but leave the Test object creation parts (Binding ViewModel objects to View structure is what it's about).
Within your PopulateTestMenu method the menus and menu structure need to be specified in the View while the data populating them needs to be created and formatted in the ViewModel.
In the View you will bind the appropriate object parts to the menu structure, and the ViewModel will automatically fill it in with the model objects when the model is bound to the view.
Looking at the code, it appears that your Test object is your ViewModel, and the Menu and MenuItem structure needs to be created in the View, then you specify the binding of the specific properties of the Test object to the specific structure parts of the Menu within the View.
I'm still in the learning Phase of WPF, EF and MVVM and now I got the following problem. I can delete and insert new items in my DataGridView but I don't know how to update my items.
All I do is select an emptyrow which already has a primary key and then I put the data into it. It's working (updating database) but the GridView is not refreshing. I Need to restart the program first to see my updated data.
My Execute Command to Update my Database. I'm in the ViewModel class
public void ExecuteUpdate(object obj)
{
try
{
SelectedIndex.Child_Update(new Farbe { FarbauswahlNr = SelectedIndex.FarbauswahlNr, Kurztext = SelectedIndex.Kurztext, Ressource = SelectedIndex.Ressource, Vari1 = SelectedIndex.Vari1, Vari2 = SelectedIndex.Vari2 });
//ListeAktualisieren --> Refreshing the List
ListeAktualisieren();
}
catch (Exception e)
{
MessageBox.Show(e.ToString());
}
}
Here is my Refresh Method which SHOULD Refresh the GridView. I'm in the ViewModel class
public void ListeAktualisieren()
{
farbliste.ListeAktualisieren(db);
farbliste.Model = farbliste.Model.Concat(farbliste.Addlist).ToList();
Model = farbliste.Model;
farbliste.Addlist.Clear();
}
The method is calling my Business List which also got a Refresh Method. Reading from my database here. I'm in the Business List class
public void ListeAktualisieren(TestDBEntities db)
{
Model.Clear();
foreach (var item in db.Farben)
{
//Insert and delete working
add = new Farbe { FarbauswahlNr = item.FarbauswahlNr, Kurztext = item.Kurztext, Ressource = item.Ressource, Vari1 = Convert.ToBoolean(item.Var1), Vari2 = item.Vari2 };
Addlist.Add(add);
}
}
Model is the Source of my GridView which is not Refreshing changed data when Updated but is showing new data rows when inserting or deleting.
You need Observablecollections and Classes with implemented INotifyPropertyChanged. Add the new element to the Observablecollection by insert and raise the event propertychanged by a change.
The rest should be done by WPF.
Edit: The Sourcecollection for the DataGrid needs to be the Observablecollection.
Edit2: To be nice I put the result of the comments here ;-)
Each row of the DataGrid is an element of the collection. Each cell of one row listens to a PropertyChangedEvent of its element (the String is Casesensitive so be carefull). If the getter of the property isn't called after the propertychangedevent the binding didn't receive the event.
This piece of Code can help asure that you don't call with nonexistent strings:
private void VerifyPropertyName(string PropertyName)
{
if (string.IsNullOrEmpty(PropertyName))
return;
if (TypeDescriptor.GetProperties(this)(PropertyName) == null) {
string msg = "Ungültiger PropertyName: " + PropertyName;
if (this.ThrowOnInvalidPropertyName) {
throw new isgException(msg);
} else {
Debug.Fail(msg);
}
}
}
Try adding this to your binding section
ItemsSource="{Binding Path=Model, UpdateSourceTrigger= PropertyChanged"}
I am using WCF Services
I have this problem:
When I retrieve data from server for my GridView at the start of an async function call, I set IsBusy = "True" . After the method is called, I set IsBusy = "False". During the method call RadBusyIndicator does not Display. I cannot understand what the problem is.
I have uploaded a simple project with this problem. Can you check it? Download
I moved the loading in a BackgroundWorker, can you try this:
private void LoadData()
{
//Activate BudyIndicator
App.Instance.SetBusy();
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (o, ea) =>
{
ObservableCollection<CustomerModel> LoadedCustomers = null;
//Create Client
proxy.ServicesClient client = new proxy.ServicesClient();
ObservableCollection<Customer> customers = client.GetCustomers();
LoadedCustomers = new ObservableCollection<CustomerModel>();
foreach (var item in customers)
{
LoadedCustomers.Add(new CustomerModel()
{
CustomerId = item.CustomerId,
Title = item.Title,
FirstName = item.FirstName,
MiddleName = item.MiddleName,
LastName = item.LastName,
CompanyName = item.CompanyName,
SalesPerson = item.SalesPerson,
EmailAddress = item.EmailAddress,
Phone = item.Phone
});
}
client.Close();
//Define return value
ea.Result = LoadedCustomers;
};
worker.RunWorkerCompleted += (o, ea) =>
{
//Get returned value
ObservableCollection<CustomerModel> model = ea.Result as ObservableCollection<CustomerModel>;
if (model != null)
{
Customers = model;
}
//Desactivate BusyIndicator
App.Instance.UnSetBusy();
};
worker.RunWorkerAsync();
}
Ok I see your problem. The Close method on your proxy wait the result of the asynch call.
Just move your client.Close(); in the GetCustomersCompleted method and this will work. (Tested with your sample)
private proxy.ServicesClient client = null;
private void LoadData()
{
App.Instance.SetBusy();
client = new proxy.ServicesClient();
client.GetCustomersCompleted += (s, e) =>
{
if (e.Error != null)
{
throw new Exception();
}
else
{
Customers = new ObservableCollection<CustomerModel>();
foreach (var item in e.Result)
{
Customers.Add(new CustomerModel()
{
CustomerId = item.CustomerId,
Title = item.Title,
FirstName = item.FirstName,
MiddleName = item.MiddleName,
LastName = item.LastName,
CompanyName = item.CompanyName,
SalesPerson = item.SalesPerson,
EmailAddress = item.EmailAddress,
Phone = item.Phone
});
}
OnPropertyChanged("Customers");
}
client.Close();//Close after the return
App.Instance.UnSetBusy();
};
client.GetCustomersAsync();
//client.Close();
}
}
If your window xaml is not inside the busy indicator it may not show. With that control you need to put the content you wish to be masked when the busy indicator is set to true inside the indicator tags. If you UserControl's primary display item is a Grid then wrap the grid in the busy indicator tags.
<UserControl>
<telerik:RadBusyIndicator IsBusy={Binding Busy}>
<Grid>
content...
</Grid>
</telerik:RadBusyIndicator>
</UserControl>
This should give you the result you are looking for.
If you are using a binding for the IsBusy property on the controller, which I assume you are, then you must implement the INotifyPropertyChanged interface so that when the value of the binding property is changed the UI is notified of that change and updates itself.
Your view model should have a property with a setter as follows:
public bool Busy
{
get{return _Busy;}
set
{
if(value != _Busy)
_Busy = value;
OnPropertyChanged("Busy");
}
}
This will notify the UI of the change;
If you are doing this already then I will need to see more of the relevant code to help more.
After I looked at your last post again if you are setting the IsBusy property to a string value that is your problem as that property takes a Boolean value.
I assume based on your code you wish to only put the busy indicator over the content control on the main window. My recommendation is to create a view model for the main window and use it as the datacontext for the page. I would also set up a property on the view model as explained above and set up a binding to that property. In the view model you can make the async call to the data store and on the return fill a collection property (recommend ObservableCollection) and bind your ListBox's IitemsSource property to that.
I hope this helpls