Binding objects and cancel button - c#

I've just read this question:
How to cancel an edit to an object using MVVM?
I have the exact same question and would like to have a simple solution. The first one looked very promising, however, I'm using entity framework and my classes are automatically generated, so that's not an option.
How can I do this easily with EF?
EDIT:
My ViewModel:
public List<Player> Players
{
get { return repository.Players.OrderBy(x => x.Firstname).ToList(); }
}
public Player CurrentPlayer
{
get { return currentPlayer; }
set
{
if (currentPlayer != value)
{
currentPlayer = value;
RaisePropertyChanged("CurrentPlayer");
}
}
}
Players is bound to a datagrid, CurrentPlayer to the selecteditem of that. Below the datagrid, I have textboxes where the user can edit the player info.
When the user presses the save button, this code is executed:
private void SaveExecute(object parameter)
{
repository.SavePlayer(currentPlayer);
Editing = false;
}
Very easy. When the user presses the cancel button, this is executed:
private void CancelExecute(object parameter)
{
if (currentPlayer.Id == 0) // id = 0 when a new player is being added
{
CurrentPlayer = null;
}
else
{
// here, the CurrentPlayer should be set back to it's previous state.
}
Editing = false;
}
CurrentPlayer is an object of Player, which is an entity class generated by EF.

I don't understand the problem. If the user is editing a new item (State == ObjectState.Added) then you discard that, (and maybe set the CurrentPlayer to what it was before pressing the "New" Button?), else just retrieve the entity from the database again and that's it...
A better way to solve this problem is to have your CRUD and your List VMs have separate instances of the entity.
For example, when I create a List view (Datagrid or otherwise), usually the data displayed in that is just a subset of the whole data displayed in the full CRUD View. So, in order to show the entity in the CRUD, I need to Get() the entity again using the necessary Includes. This resolves the whole cancel problem, because the entity instance you're modifying is actually not the same as the one shown in the List view. If the user presses Save, you can replace the instance shown in the list view with the edited one, and if the user presses cancel, don't do anything.
Edit: Also be aware that if your entities are being generated by a T4 Template such as the Entity Framework STE Template, you can modify the .tt file and customize it to generate whatever code you need in your entities.

Related

CRCase_RowSelecting fires with next case on subsequent save

I created an extension for CRCaseMaint, and added the event CRCase_RowSelecting. Here is the code I am currently using:
protected virtual void CRCase_RowSelecting(PXCache sender, PXRowSelectingEventArgs e)
{
CRCase row = e.Row as CRCase;
if (row == null) return;
PXDatabase.ResetSlot<List<CRCase>>("OriginalCase");
List<CRCase> originalCaseSlot = PXDatabase.GetSlot<List<CRCase>>("OriginalCase");
if (originalCaseSlot.Count == 0)
{
originalCaseSlot.Add(sender.CreateCopy(row) as CRCase);
}
else
{
originalCaseSlot[0] = sender.CreateCopy(row) as CRCase;
}
}
When I first open a case, this event will fire a couple times, and the last time it fires, the current case is correctly stored in e.Row, so this code works great. When I click Save, I have a RowPersisting event that compares the case stored in the originalCaseSlot with the updated case. At the end, it sets the original case slot to the updated case. This also works well.
However, when I make another change without leaving the case, and click save, e.Row on the RowSelecting event now has the next case stored on it rather than the current case. Since I am not touching the next case in any way, I am surprised that this is happening.
My question is, should I be using a different event instead of RowSelecting, or is there something else I am missing?
Thank you all for your help.
Sometimes when the primary record gets updated or the user clicks on a form toolbar button, the framework selects 2 records from database: the current primary record and the next one. This is why RowSelecting is invoked 2nd time for the next CRCase record.
Honestly, using PXDatabase Slots to store user session-specific records is not a good idea. PXDatabase Slots are shared among all user sessions and should only be used to cache frequently used data from database, which is not prone to frequent updates. This makes the main purpose of PXDatabase Slots to reduce number of database queries to widely and very often used configurable data, like Segment Key or Attribute configurations.
With that said, using the RowSelecting handler is definitely a step in the right direction. Besides, the RowSelecting handler, you should additionally define a separate PrevVersionCase data view to store the original CRCase record(s) and also override the Persist method to report about changes. The Locate method used on PXCache objects searches the cache for a data record that has the same key fields as the provided data record. This approach allows to compare changes between the originally cached and modified CRCase records having identical key field values.
public class CRCaseMaintExt : PXGraphExtension<CRCaseMaint>
{
[Serializable]
public class CRPrevVersionCase : CRCase
{ }
public PXSelect<CRPrevVersionCase> PrevVersionCase;
protected virtual void CRCase_RowSelecting(PXCache sender, PXRowSelectingEventArgs e)
{
CRCase row = e.Row as CRCase;
if (row == null || e.IsReadOnly) return;
var versionCase = new CRPrevVersionCase();
var versionCache = PrevVersionCase.Cache;
sender.RestoreCopy(versionCase, row);
if (versionCache.Locate(versionCase) == null)
{
versionCache.SetStatus(versionCase, PXEntryStatus.Held);
}
}
[PXOverride]
public void Persist(Action del)
{
var origCase = Base.Case.Current;
var origCache = Base.Case.Cache;
CRPrevVersionCase versionCase;
if (origCache.GetStatus(origCase) == PXEntryStatus.Updated)
{
versionCase = new CRPrevVersionCase();
origCache.RestoreCopy(versionCase, origCase);
versionCase = PrevVersionCase.Cache.Locate(versionCase) as CRPrevVersionCase;
if (versionCase != null)
{
foreach (var field in Base.Case.Cache.Fields)
{
if (!Base.Case.Cache.FieldValueEqual(origCase, versionCase, field))
{
PXTrace.WriteInformation(string.Format(
"Field {0} was updated", field));
}
}
}
}
del();
if (origCase != null)
{
PrevVersionCase.Cache.Clear();
versionCase = new CRPrevVersionCase();
Base.Case.Cache.RestoreCopy(versionCase, origCase);
PrevVersionCase.Cache.SetStatus(versionCase, PXEntryStatus.Held);
}
}
}
public static class PXCacheExtMethods
{
public static bool FieldValueEqual(this PXCache cache,
object a, object b, string fieldName)
{
return Equals(cache.GetValue(a, fieldName), cache.GetValue(b, fieldName));
}
}

C# WPF inotifypropertychanged interferes with data entry

I'm not sure if I'm asking this properly. I have a datagrid where column 1 data can come from two different places depending on a switch bit. The first place is a periodic update of the view model column from the model. The second place is if I manually edit a cell of column 1. I have a custom column binding in the XAML code binds to a list of classes. The problem is that when I start typing in data, it gets overwritten by the periodic update. Is there a proper way to design what I am looking to accomplish?
Below is the pseudo code for what is bound to Column 1 of the datagrid.
public int Column1
{
get
{
if(mySwitch)
return this.NumList[0].Data;
else
return this.NumList[0].EditedData;
}
set
{
if (mySwitch)
{
//this gets set every few seconds
this.Numlist[0].Data = value;
this.OnPropertyChanged("mydata");
}
else
{ //this gets called only when mySwitch is false and I'm in edit mode.
this.Numlist[0].EditedData = value;
this.OnPropertyChanged("myediteddata");
}
}
}
Thanks

Data Validation not working in Edit Mode

I want to implement validation on text box for whether the name exists in the database. I am using wpf with c#. I have implemented a validation on the text box while saving new data. My problem is in Edit Mode: when I go to edit mode and try to save, an error appears that the name already exist.
The Below Code works fine on save mode But when it comes to Edit mode when datas get binding the error message shows.
pls suggest me a good way to implement the validation that work on edit mode too.
class MyParent
{
public MyCarClass CurrentCarEntity {get; set;}
private void txtName_TextChanged(object sender, RoutedEventArgs e)
{
CurrentCarEntity.Name = txtName.Text.Trim();
var getName = //Code for getting name from local db
if(CurrentCarEntity.Name != Null)
{
if(getName.Equals(CurrentCarEntity.Name))
{
MessageBox.Show("Name Already Exists");
}
}
}
}
Looks like you're making validation fail for the entire form if the name already exists - validation will trigger every time you try to submit (edit, insert, etc) so edits will always fail.
I would make two textboxes, one for inserts and one for edits. Hide the insert box while in edit mode, or if you want to stick with one, at least disable the validator when editing.
It seems that you are following the wrong approach
let us assume we have a class called users like following
public class User: IValidatableObject
{
public int Id{get; set;}
public string UserName{get; set;}
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(string.IsNullOrEmpty(UserName))
yield return new ValidationResult("Username field is required!", new string[]{"UserName"});
else
{
// check if another User has the same username already
var db= new YourDbContext();
var exists=db.Users.FirstOrDefault(t=>t.Id!=Id && t.UserName.ToLower()=UserName.ToLower());
if(exists!=null)
yield return new ValidationResult("Username is already used by another user!", new string[]{"UserName"});
}
}
}
you don't need to worry about the edit or create, since in both cases you are checking the database if the Users table contains another user,and not same user you are creating or editing, has the same username.
hope this will help you

Delete data using 2 DialogForm

hi i have question for remove data from 2 forms dialog
in first dialogform it's contain list of data, and in second dialogform it's contain detail of data and delete button... i already successfully delete data in database but i confused how to remove data from list...
if just select data and delete i know it's can be done with this code
quizzes.RemoveAt(listBoxQuizzes.SelectedIndex);
but problem here in dialogform1 not available button delete, just view details data. so if user want to delete data, he must open dialogform2 (detail data)
i already done delete data in database with this code
Global.deleteData("DELETE FROM Quiz_Occurrences WHERE ID = " + id);
and close detaildataform (dialogform2) by
this.Close();
and move to dialogform1 (listdatabox)
the problem in here, data which just i delete still in there because it's still not remove yet (already delete from database but not remove from list). and need to restart program to see effect of delete data
Update Progress
i changed data to global var, so it's technically i can remove data in dialogform2
this is code (modifier listbox in dialogform1)
int no = 1;
foreach (CQuizOccurrence myQuizOccurrence in Global.quizOccurrences) {
}
if i want to delete it from dialogform1, i can use
Global.quizOccurrences.removeAT(listBoxQuizzes.SelectedIndex);
but if i want to delete it from dialogform2
Global.quizOccurrences.removeAT(.........); //still not have idea how can i reference index
Update solution from #nitin
so first i write in formdialog2
public Frmdialog1 frm_dialog { get; set; }
then i write this in formdialog1
frmdialog2.frm_dialog=this;
then back again to formdialog1 to write
frm_dialog.quizzes.RemoveAt(frm_dialog.listBoxQuizzes.SelectedIndex);
is that right because i get many error
If you are opening second dialog from first one u can Have property in Frmdialog2 like
public Frmdialog1 frm_dialog { get; set; }
After creating object of Frmdialog2 in Frmdialog1 you can set this property as
frmdialog2.frm_dialog=this;
Now u can remove item from this listbox in Frmdialog2 iteself after deleting record from database as
frm_dialog.quizzes.RemoveAt(frm_dialog.listBoxQuizzes.SelectedIndex);
NOTE: Modifier for your listbox should be public
i'm finally be able to do as i want after ask many different question about this topic
first i try to change var to global, so i can remove data in listbox dialogform1 from dialogform2 (i think it's the easiest way)
//in dialog form1
foreach (CQuizOccurrence myQuizOccurrence in Global.quizOccurrences) {
//load data from Global.quizOccurences
}
//call function close to close dialogform1
then in dialogform2, match Global.quizOccurrences data with date&time detail data (using list & foreach)
List<CQuizOccurrence> matchData = new List<CQuizOccurrence>();
foreach (CQuizOccurrence myQuizOccurrence in Global.quizOccurrences)
{
DateTime dtDatabase = (DateTime)myQuizOccurrence.occurred;
string dt = dtDatabase.ToString();
if (dt == dateOccur) {
matchData.Add(myQuizOccurrence);
}
}
foreach (CQuizOccurrence myQuizOccurrence in matchData)
{
Global.quizOccurrences.Remove(myQuizOccurrence);
}
//call function show dialog for formdialog1
form1 could subscibe to the form2's close event.
inside form1
form2 f2dialog = new form2(/*I guess you are passing data here*/);
f2.dialog.Closing += eventhandler;
somewhere else
void eventhandler(object sender, eventargs e)
{
//refresh globaldata since by now you have ran delete query
//rebind or call listbox.items.refresh() or both <-------------this how do you get data from rver? the server is updated but does global know that?
}
then you need to call code to get data from database again. and rebind to data
listbox.datacontext = Global.GetData();//or however this is done
you have to manually reset this eveerytime you change your database
databinding is not as smart as you think it is.

Copying a TabItem with an MVVM structure

This is an attempt to expand on this question. In my WPF program I've been cloning tabItems by using an XamlWriter in a function called TrycloneElement. I originally found this function here, but the function can also be viewed in the link to my previous question.
Now that I am beginning to worry about functionality inside my program, I found that the TrycloneElement function does not replicate any code-behind functionality assigned to the tabItem that it is cloning.
Because of High Core's link and comment on my earlier question I decided to start implementing functionality on my tabItems through Data Binding with my ViewModel.
Here is a sample of a command that I've implemented:
public viewModel()
{
allowReversing = new Command(allowReversing_Operations);
}
public Command AllowReversing
{
get { return allowReversing; }
}
private Command allowReversing;
private void allowReversing_Operations()
{
//Query for Window1
var mainWindow = Application.Current.Windows
.Cast<Window1>()
.FirstOrDefault(window => window is Window1) as Window1;
if (mainWindow.checkBox1.IsChecked == true) //Checked
{
mainWindow.checkBox9.IsEnabled = true;
mainWindow.groupBox7.IsEnabled = true;
}
else //UnChecked
{
mainWindow.checkBox9.IsEnabled = false;
mainWindow.checkBox9.IsChecked = false;
mainWindow.groupBox7.IsEnabled = false;
}
}
*NOTE: I know that I cheated and interacted directly with my View in the above code, but I wasn't sure how else to run those commands. If it is a problem, or there is another way, please show me how I can run those same commands without interacting with the View like I did.
Now to the question:
After changing my code and adding the commands to my ViewModel, the TrycloneElement function no longer works. At run time during the tab clone I receive an XamlParseException on line, object x = XamlReader.Load(xmlReader); that reads:
I'm fine with ditching the function if there is a better way and I don't need it anymore. But ultimately, how do I take a tabItem's design and functionality and clone it? (Please keep in mind that I really am trying to correct my structure)
Thank you for your help.
Revision of Leo's answer
This is the current version of Leo's answer that I have compiling. (There were some syntax errors)
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) })
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd
where dpd != null
select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly)
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
Here is my example of a properly-implemented dynamic TabControl in WPF.
The main idea is that each Tab Item is a separate widget that contains its own logic and data, which is handled by the ViewModel, while the UI does what the UI must do: show data, not contain data.
The bottom line is that all data and functionality is managed at the ViewModel / Model levels, and since the TabControl is bound to an ObservableCollection, you simply add another element to that Collection whenever you need to add a new Tab.
This removes the need for "cloning" the UI or do any other weird manipulations with it.
1.) To fix that XamlParseException, make sure you have a public constructor like an empty one, you probably defined a constructor and when you tried to serialize that object and deserialize it can't. You have to explicitly add the default constructor.
2.) I don't like the word clone, but I'd say, when they want to copy. I'll manually create a new tab item control then do reflection on it.
I have this code that I made
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] {new PropertyFilterAttribute(PropertyFilterOptions.SetValues)})
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd where dpd != null select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly))
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
So it would be like
var newTabItem = new TabItem();
newTabItem.CopyPropertiesFrom(masterTab);

Categories