Catel Cancel button in DataWindow - catel

Then I modify "ProductName" and press "Cancel" button property is reset to passed parameter. But if i modify ProgramIds (add, or delete) and press "Cancel" button collection no set to passed. Why?
I have in ViewModel:
[Model]
[Catel.Fody.Expose("ProductName")]
[Catel.Fody.Expose("ProgramIds")]
Public ProgramDataModel DataModel
{
get { return GetValue<ProgramDataModel>(DataModelProperty); }
set { SetValue(DataModelProperty, value); }
}
In Model:
public string ProductName
{
get { return GetValue<string>(ProductNameProperty); }
set { SetValue(ProductNameProperty, value); }
}
public static readonly PropertyData ProductNameProperty = RegisterProperty(nameof(ProductName), typeof(string));
public ObservableCollection<ProgramIDModel> ProgramIds
{
get { return GetValue<ObservableCollection<ProgramIDModel>>(ProgramIdsProperty); }
set { SetValue(ProgramIdsProperty, value); }
}
public static readonly PropertyData ProgramIdsProperty = RegisterProperty(nameof(ProgramIds), typeof(ObservableCollection<ProgramIDModel>));
In MainViewModel:
var viewModel = new ProductWindowViewModel(DataViewModel);
await _uiVisualizerService.ShowDialogAsync(viewModel);

Catel uses IEditableObject in order to restore the state when the user hits cancel. Because you are using a collection of ids, Catel might have some issues resetting the collection.
Please report this issue (with repro) at the official issue tracker.
As a workaround for now, you can override the CancelAsync method and revert the collection there yourself. Or as an alternative you can use a cloned collection in the VM and only replace the items in SaveAsync.

faced exactly the same problem with an ObservableCollection, simply resolved it by adding [Serializable] Attribute to the model class
[Serializable]
public class ProgramDataModel:ValidatableModelBase
{
}

Related

Problem with custom processing button to generate NoteIDs

Creating a new dummy maintenance screen to enable universal search on item cross references and gotten as far as creating the new DAC and BLC, even adding the Processing buttons to the screen, but when clicking the either processing button nothing happens (currently have it throwing a PXException). Ultimately I need to use the "Process All" (labeled "Create NoteIDs") button to fill in the NoteID field so I can actually work on the universal search part.
This is my graph. The INItemXRef is actually a new custom DAC with a NoteID field attached (no other changes, created directly from the database). Ultimately I need to update the code below to fill in random values to empty NoteIDs, so if there is any advice on the next step that would also be appreciated after solving the immediate problem:
public class INItemXRefGraph : PXGraph<INItemXRefGraph>
{
public PXSelect<INItemXRef> INItemXRef;
public PXSave<INItemXRef> Save;
public PXFilter<INItemXRef> MasterView;
[PXFilterable]
public PXProcessing<INItemXRef, Where<INItemXRef.noteID, IsNull>> INDocumentList;
public INItemXRefGraph()
{
INDocumentList.SetProcessDelegate(ReleaseDoc);
INDocumentList.SetProcessAllCaption("Create NoteIDs");
}
public static void ReleaseDoc(System.Collections.Generic.List<INItemXRef> list)
{
throw new PXException("Hello World");
}
Try this
public class INItemXRefGraph : PXGraph<INItemXRefGraph>
{
public PXSelect<INItemXRef> INItemXRef;
public PXSave<INItemXRef> Save;
public PXFilter<INItemXRef> MasterView;
[PXFilterable]
public PXProcessing<INItemXRef, Where<INItemXRef.noteID, IsNull>> INDocumentList;
public INItemXRefGraph()
{
INDocumentList.SetProcessDelegate(
delegate(System.Collections.Generic.List<INItemXRef> list)
{
System.Collections.Generic.List<INItemXRef> newlist = new System.Collections.Generic.List<INItemXRef>(list.Count);
foreach (INItemXRef doc in list)
{
newlist.Add(doc);
}
ReleaseDoc(newlist);
}
);
INDocumentList.SetProcessAllCaption("Create NoteIDs");
}
public static void ReleaseDoc(System.Collections.Generic.List<INItemXRef> list)
{
throw new PXException("Hello World");
}

How should one implement a change state dialog with undo/redo support in Catel?

I cannot get Undo and Redo to behave correctly when using a dialog.
I have a simple model with a property indicating the state of the object(running, paused, stopped) which can be altered via a dialog. What happens is that I get actions that seems to do nothing in my undo queue or undo restores the object to an intermediate state.
The model object is registered with memento in the constructor. The dialog has three radio buttons each representing one of the three different states. Each radio button is bind to a command each. Each command performs a change of the property. I have tried two different approaches, either each command sets the property directly in the object or each command sets an instance variable for the view model when called and then I use the Saving event to modify the object.
If using the first approach each property change is put on the Undo queue if the user clicks on more than just one radiobutton before clicking Ok in the dialog. Tried to solve that by wrapping the whole dialog into a batch but that results in undoing the state change the object is restored to the state it had before the final one, i.e. if the property was set to stopped before the dialog opened and the user pressed the pause radiobutton, then start one and finally Ok, undo will set the property to paused instead of the expected stopped.
If using the second approach the user opens the dialog, change the state to paused, click Ok in the dialog the undo/redo behaves as expected but if the dialog is opened again and Cancel is chosen one more action is added to the Undo queue, i.e. the user has to click Undo twice to get back to the initial stopped-state.
So my question is how should this be correctly implemented to get the expected behaviour; that each dialog interaction can be undone and not every interaction in the dialog?
Here is the code for the ViewModel:
namespace UndoRedoTest.ViewModels
{
using Catel.Data;
using Catel.MVVM;
public class StartStopViewModel : ViewModelBase
{
Machine.MachineState _state;
public StartStopViewModel(Machine controlledMachine)
{
ControlledMachine = controlledMachine;
_state = controlledMachine.State;
StartMachine = new Command(OnStartMachineExecute);
PauseMachine = new Command(OnPauseMachineExecute);
StopMachine = new Command(OnStopMachineExecute);
Saving += StartStopViewModel_Saving;
}
void StartStopViewModel_Saving(object sender, SavingEventArgs e)
{
ControlledMachine.State = _state;
}
[Model]
public Machine ControlledMachine
{
get { return GetValue<Machine>(ControlledMachineProperty); }
private set { SetValue(ControlledMachineProperty, value); }
}
public static readonly PropertyData ControlledMachineProperty = RegisterProperty("ControlledMachine", typeof(Machine));
public override string Title { get { return "Set Machine state"; } }
public Command StartMachine { get; private set; }
public Command PauseMachine { get; private set; }
public Command StopMachine { get; private set; }
private void OnStartMachineExecute()
{
_state = Machine.MachineState.RUNNING;
//ControlledMachine.SecondState = Machine.MachineState.RUNNING;
}
private void OnPauseMachineExecute()
{
_state = Machine.MachineState.PAUSED;
//ControlledMachine.SecondState = Machine.MachineState.PAUSED;
}
private void OnStopMachineExecute()
{
_state = Machine.MachineState.STOPPED;
//ControlledMachine.SecondState = Machine.MachineState.STOPPED;
}
}
}
First of all, don't subscribe to the Saving event but simply override the Save() method. Note that Catel handles the model state for you when you decorate a model with the ModelAttribute. Therefore you need to get the prestate and poststate of the dialog and then push the result set into a batch.
For example, I would create extension methods for the object class (or model class) like this:
public static Dictionary<string, object> GetProperties(this IModel model)
{
// todo: return properties
}
Then you do this in the Initialize and in the Save method and you would have 2 sets of properties (pre state and post state). Now you have that, it's easy to calculate the differences:
public static Dictionary<string, object> GetChangedProperties(Dictionary<string, object> preState, Dictionary<string, object> postState)
{
// todo: calculate difference
}
Now you have the difference, you can create a memento batch and it would restore the exact state as you expected.
ps. it would be great if you could put this into a blog post once done or create a PR with this feature

Cannot capture javafx CheckBoxTableCell CellEditEvent

I have defined a CheckBoc TableColumn as
#FXML private TableColumn<Batch, Boolean> sltd;
And have defined the CellValueFactory & CellFactory
sltd.setCellValueFactory(new PropertyValueFactory<Batch, Boolean>("pr"));
sltd.setCellFactory(CheckBoxTableCell.forTableColumn(sltd));
My problem is i am not able to capture the edit column event for the checkbox. I use the following code:
sltd.setOnEditStart(new EventHandler<TableColumn.CellEditEvent<Batch, Boolean>>() {
#Override
public void handle(TableColumn.CellEditEvent<Batch, Boolean> t) {
//System.out.println("CheckBox clicked.");
}
});
I don't think the check boxes in the CheckBoxTableCell invoke the startEdit(...) method on the table.
The only thing that can happen in an edit is that the boolean property of one of the items in the table changes from true to false, or vice versa. So you can check for this just by listening directly to those boolean properties.
If you want a single listener that will catch changes to any of the properties, you can create an observableList with an "extractor" and register a list change listener with the list. This looks like:
ObservableList<Batch> items = FXCollections.observableArrayList(new Callback<Batch, Observable[]>() {
#Override
public Observable[] call(Batch batch) {
return new Observable[] { batch.prProperty() } ;
}
}
// populate items
table.setItems(items);
items.addListener(new ListChangeListener<Batch>() {
#Override
public void onChanged(Change<? extends Batch> change) {
while (change.hasNext()) {
if (change.wasUpdated()) {
System.out.println("Item at "+change.getFrom()+" changed value");
}
}
}
});

Assigning an async result to a data binding property

Below is a sample implementation that uses metro API and data binding (using MVVM) to populate list of folders in a drop down list.
The View model‘s constructor uses SetFolders method (private async), which calls an awaitable method fileService.GetFoldersAsync() to get list of folders. The folders list is then gets assigned to the property called “FoldersList”. XAML uses this property to populate a drop down list using the data binding.
I wonder is there a better way to set the FoldersList property without having to set it in the constructor as below. I would prefer to call the GetFilesAsync method and set the FilesList property value, when the actual data binding occurs (not during the class init). Since the properties do not support async/await modifiers (as far as I know) I’m struggling to implement a proper solution. Any ideas greatly appreciated.
The code is below.
ViewModel
public class FileViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private readonly IFileService fileService;
public FileDataViewModel(IFileService fileService)
{
this.fileService = fileService;
SetFolders();
}
private async void SetFolders ()
{
FoldersList = await fileService.GetFoldersAsync();
}
private IEnumerable< IStorageFolder > foldersList;
public IEnumerable<StorageFolder> FoldersList
{
get { return foldersList; }
private set
{
foldersList = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("FoldersList"));
}
}
}
}
IFileService and implementation
public interface IFileService {
Task<IEnumerable<IStorageFolder>> GetFilesAsync();
}
public class FileService : IFileService
{
public async Task<IEnumerable<IStorageFolder>> GetFoldersAsync()
{
var folder = KnownFolders.DocumentsLibrary;
return await folder.GetFoldersAsync();
}
}
I would implement it as a lazy property and use ObservableCollection<T> rather than IEnumerable<T>. We are doing it in several projects and it works well. This way you can guarantee that you are loading data only when needed. Furthermore, if you need to prefetch it, you can always call the load method in the constructor or elsewhere.
As a side note, I personnaly wouldn't expose IStorageFolder directly from my ViewModels.
private async Task LoadData()
{
if(!IsLoading)
{
IsLoading = true;
Folders = new ObservableCollection<Folder>(await fileService.GetFolderAsync());
}
IsLoading = false;
}
private ObservableCollection<Folder> _folders;
public ObservableCollection<Folder> Folders
{
get
{
if(_folders == null)
{
LoadData();//Don't await...
}
return _folders;
}
private set
{
SetProperty(ref _folders,value);
}
}
private bool _isLoading;
public bool IsLoading
{
get
{
return _isLoading;
}
private set
{
SetProperty(ref _isLoading,value);
}
}
Note that you can use the IsLoading property to display a progress ring for instance. after that the observable collection is loaded, you will be able to refresh it without recreating it. (_folders.Add, _folders.Remove, _folders.Clear...)

Orchard CMS - new properties not updating after migration

I am writing a custom module that retrieves and pushes data directly from the Orchard DB using an injected IRepository.
This works fine until i need to update a content part. I add an update in my migrations class and the update runs through (DB schema updated with default values), however I can't update any of the new values through IRepository. I have to drop down into the NHibernate.ISession to flush the changes through.
This all works fine on a newly created recipe, it's only when i alter a part. Here are the key code snippets:
public class TranslationsPartRecord : ContentPartRecord
{
internal const string DefaultProductName = "Product";
public TranslationsPartRecord()
{
ProductName = DefaultProductName;
}
public virtual string ProductName { get; set; }
}
public class TranslationsPart : ContentPart<TranslationsPartRecord>
{
public string ProductName
{
get { return Record.ProductName; }
set { Record.ProductName = value; }
}
}
public class TranslationsHandler : ContentHandler
{
public TranslationsHandler(IRepository<TranslationsPartRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
}
}
public class Migrations : DataMigrationImpl
{
public int Create()
{
SchemaBuilder.CreateTable("TranslationsPartRecord", table => table
.Column<int>("Id", column => column.PrimaryKey().Identity())
.Column("ProductName", DbType.String, column => column.NotNull().WithDefault(TranslationsPartRecord.DefaultProductName))
);
return 1;
}
public int UpdateFrom1()
{
SchemaBuilder.AlterTable("TranslationsPartRecord", table => table.AddColumn("ProductDescription", DbType.String, column => column.NotNull().WithDefault(TranslationsPartRecord.DefaultProductDescription)));
return 2;
}
}
When i add the second property "ProductDescription" in this example, after the update is run the columns appear in the DB but i cannot update them until i recreate the Orchard recipe (blat App_Data and start again).
here's how I am trying to update:
// ctor
public AdminController(IRepository<TranslationsPartRecord> translationsRepository)
{
_translationsRepository = translationsRepository;
}
[HttpPost]
public ActionResult Translations(TranslationsViewModel translationsViewModel)
{
var translations = _translationsRepository.Table.SingleOrDefault();
translations.ProductName = translationsViewModel.ProductName;
translations.ProductDescription = translationsViewModel.ProductDescription;
_translationsRepository.Update(translations);
_translationsRepository.Flush();
}
and here's the NHibernate "fix":
var session = _sessionLocator.For(typeof(TranslationsPartRecord));
var translations = _translationsRepository.Table.SingleOrDefault();
// is translations.Id always 1?
var dbTranslations = session.Get<TranslationsPartRecord>(translations.Id);
dbTranslations.ProductName = translationsViewModel.ProductName;
dbTranslations.ProductDescription = translationsViewModel.ProductDescription;
session.Update(dbTranslations);
session.Flush();
which seems a bit kludgey...
Cheers.
ps i'm still running Orchard 1.3.9
pps after more testing, the NHibernate fix has stopped working now, so perhaps my initial findings were a red herring. It seems as though new properties on the content part are totally ignored by NHibernate when updating/retrieving - as though the object definition is cached somewhere...
If your mappings aren't being updated that is strange. You can try to force it by deleting the mappings.bin in the app_data folder, and restarting the application. Orchard should recreate the nhibernate mappings and save as mappings.bin.
I have ran into the same issue, and the only way around it that I can find is to delete mappings.bin (I don't need to disable and re-enable the module). In fact, this is the answer that I got from Bertrand when I asked why this was happening.
I have logged this as an issue at http://orchard.codeplex.com/workitem/19306. If you could vote this up, then we may get it looked at quicker.
This seems like a similar issue to what I am seeing... I am seeing that when you enable a module, it runs the NHibernate mappings BEFORE running the Migrations..
https://orchard.codeplex.com/workitem/19603
Josh
Update the hash value in the ComputingHash method in the PersistenceConfiguration Class,
updating the hash value may recreate the mappings.bin file.
public class PersistenceConfiguration : ISessionConfigurationEvents
{
public void Created(FluentConfiguration cfg, AutoPersistenceModel defaultModel)
{
DoModelMapping(cfg, defaultModel);
}
public void ComputingHash(Hash hash)
{
hash.AddString("Some_strings_to_update_hash");
}
private void DoModelMapping(FluentConfiguration cfg, AutoPersistenceModel defaultModel)
{
// mappings here....
}
public void Prepared(FluentConfiguration cfg) { }
public void Building(Configuration cfg) { }
public void Finished(Configuration cfg) { }
}

Resources