Silverlight 4.0 Form Initialization Problem - xaml

I am getting a very strange error on one of my Silverlight 4.0 pages. I have a form that has a "save" button which is disabled by default. This form gets populated by a bunch of user-specified defaults, which come from an asynchronous server call (MyFacade.getFormDefaults below). When the user changes one of the fields (after it's populated), I want the "save" button to become enabled.
I think I have the logic correct, but I'm getting a very strange error that I can't find much useful information on. The error is: System.InvalidOperationException: The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized.
Below is a very simplified version of what I have...
profile.fs:
type profile() as this =
inherit UriUserControl("/whatever;component/profile.xaml", "profile")
[<DefaultValue>]
val mutable isFormLoaded : bool
[<DefaultValue>]
val mutable btnSave : Button
[<DefaultValue>]
val mutable txtEmail : TextBox
// constructor
do
this.isFormLoaded <- false
// make the "this" values point at the XAML fields
this.btnSave <- this?btnSave
this.txtEmail <- this?txtEmail
// get the form defaults and send them to
MyFacade.getFormDefaults(new Action<_>(this.populateFormDefaults))
()
member this.populateFormDefaults (formDefaults : MyFormDefaultsUIVO array option) =
// populate this.txtEmail with the default value here
this.isFormLoaded <- true // set the form to be loaded once that's done
()
// enable the "Save" button when the user modifies a form field
member this.userModifiedForm (sender : obj) (args : EventArgs) =
// **** EXCEPTION OCCURS ON THE LINE BELOW ****
if this.isFormLoaded then
this.btnSave.IsEnabled <- true
()
profile.xaml:
<nav:Page Name="profile" Loaded="formLoaded">
<TextBox Name="txtEmail" TextChanged="userModifiedForm "/>
<Button Name="btnSave" IsEnabled="False"/>
</nav:Page>
Even if I get rid of all the isFormLoaded logic, and simply set this.btnSave.IsEnabled <- true inside of this.userModifiedForm, I get the same error. Any ideas would be greatly appreciated. Thanks.

The exception - "The initialization of an object or value resulted in an object or value being accessed recursively before it was fully initialized" - is generated by the F# runtime when an object is accessed before being fully initialized.
Accessing this - whether to check isFormLoaded or btnSave.IsEnabled - before the constructor has run will cause the error. Have you verified that userModifiedForm is only called after the constructor?

Related

Databinding is initialized in improper order by automatically generated executeBindings

Preface
I have a simple app with a viewmodel, a custom UI control, and a TextView. Databinding is setup like this:
LiveData -> control.value -> control.preValue -> TextView
When LiveData is changed, databinding notifies control.value of the new value. control.value setter has a line which also gives the new value to control.preValue. Both properties have calls to their respective databinding listeners to notify databinding that the values have changed and that the UI should be updated. The text value of TextView is bound to control.preValue, so when the listener is notified, the TextView is updated.
This works well at runtime, however there is a problem at initialization.
The Problem
When the UI is first constructed, the LiveData value is not correctly propagated to the TextView. This is because the listeners have not yet been created by the android databinding library, so when control.preValue is set by control.value's setter, the listener is still null.
Diving deeper into executeBindings we can see the cause of the problem.
executeBindings is a function which is part of the *BindingImpl file automatically generated by the databinding library based on the Binding Adapters I have defined. It is responsible for initializing databinding, e.g. creating listeners, registering livedatas, and setting initial values to the UI.
executeBindings starts like this. It initializes variables for all the databound values.
#Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.Integer viewmodelBpmGetValue = null;
androidx.lifecycle.MutableLiveData<java.lang.Integer> viewmodelBpm = null;
int bpmPickerPreValue = 0;
androidx.lifecycle.MutableLiveData<java.lang.Boolean> viewmodelPlaying = null;
java.lang.String integerToStringBpmPickerPreValue = null;
int androidxDatabindingViewDataBindingSafeUnboxViewmodelBpmGetValue = 0;
com.okos.metronome.MetViewModel viewmodel = mViewmodel;
Next, it gets the value of control.preValue property and stores it in the earlier created variable. This is already the core of the problem. At this point control.preValue is still at the default value that is defined in the control's definition class, not the LiveData value which will be assigned to it a bit later.
if ((dirtyFlags & 0x18L) != 0) {
// read bpmPicker.preValue
bpmPickerPreValue = bpmPicker.getPreValue();
// read Integer.toString(bpmPicker.preValue)
integerToStringBpmPickerPreValue = java.lang.Integer.toString(bpmPickerPreValue);
}
Next we get the LiveData value from the viewmodel and register it with databinding
if ((dirtyFlags & 0x15L) != 0) {
if (viewmodel != null) {
// read viewmodel.bpm
viewmodelBpm = viewmodel.getBpm();
}
updateLiveDataRegistration(0, viewmodelBpm);
if (viewmodelBpm != null) {
// read viewmodel.bpm.getValue()
viewmodelBpmGetValue = viewmodelBpm.getValue();
}
// read androidx.databinding.ViewDataBinding.safeUnbox(viewmodel.bpm.getValue())
androidxDatabindingViewDataBindingSafeUnboxViewmodelBpmGetValue = androidx.databinding.ViewDataBinding.safeUnbox(viewmodelBpmGetValue);
}
Here it sets control.value to the value of the LiveData in the first if block. This line will trigger the control.value setter, which will set control.preValue, and those setters will both try to call their respective onChange listeners but they will be null because executeBindings hasn't created them yet. They are created in the 2nd if block.
if ((dirtyFlags & 0x15L) != 0) {
// api target 1
this.bpmPicker.setValue(androidxDatabindingViewDataBindingSafeUnboxViewmodelBpmGetValue);
}
if ((dirtyFlags & 0x10L) != 0) {
// api target 1
com.okos.metronome.view.DialPickerBindingAdapter.setPreValueListener(this.bpmPicker, (com.okos.metronome.view.PrePickerBase.OnValueChangeListener)null, bpmPickerpreValueAttrChanged);
com.okos.metronome.view.DialPickerBindingAdapter.setValueListener(this.bpmPicker, (com.okos.metronome.view.PrePickerBase.OnValueChangeListener)null, bpmPickervalueAttrChanged);
}
Finally, the value of the TextView is set, but it is set to the original value of preValue which we cached in a variable in the very first if block. **Not the new value which has been updated to preValue from the LiveData since then.
if ((dirtyFlags & 0x18L) != 0) {
// api target 1
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvBpmDisplay, integerToStringBpmPickerPreValue);
}
This seems like an oversight in the databinding library, and I wonder if anyone has any ideas of getting around this? The fix seems pretty simple to just move the first if block in executeBindings down so integerToStringBpmPickerPreValue is set after the value has been set from LiveData, but because executeBindings is automatically generated, I can't do that. There are some ways of changing the order of execution in executeBindings, like which order the bindings are defined in the xaml, but none of that affects the parts I want to change.

Suppress unsaved changes message in Cuba Platform

I have a lookup screen with authors and I load the data by using a custom query in init from AuthorsBrowse::AbstractLookup. I manipulate the data I want to display, but the changes should not be saved.
If I close the tab i get this message dialog:
You have unsaved changes
Do you want to discard unsaved changes?
How can I suppress this message?
This message appears if anything datasource was changed.
If you want suppress message, you need to add to datasource (that was changed) attribute allowCommit="false", like that:
<collectionDatasource id="authorsDs"
class="com.haulmont.workshop.core.entity.Author"
view="_local" allowCommit="false">
<query>
<![CDATA[
select e from ws$Author e where e.status = 10
]]>
</query>
</collectionDatasource>
You can also implement a more fine-grained control over the unsaved changes behavior of your screen if the screen implements Window.Committable interface. The interface contains isModified() method which returns a boolean value.
AbstractEditor already implements this interface and its isModified() looks as follows:
#Override
public boolean isModified() {
return getDsContext() != null && getDsContext().isModified();
}
You can implement Window.Committable in your browser screen and just return false or write some logic in isModified().

Initializer threw an exception

I get an error "The type Initializer for 'Myproject' thew an exception. It's rather bizarre.
In My Main form I have the following in my Button-click event:
MyShip.Name = "Lolipop"
That one line causes the error.
MyShip is declared in a module called GlobalVariables.vb as follows:
Friend MyShip As New clsShip
clsShip has a property called Name.
I'm not sure what's happening. Oddly, I can set MyShip.Name = "Lolipop" in the form's LOAD() event, but can't do that in any of the button click events.
Ideas?

qt5 proxy model updating too soon before main model update is done

I have a setup with a main model (QStandardModel), a proxy model which changes the output of the DisplayRole, and a separate tableview displaying each model. Inside the main model data is a user role that stores a pointer to another QObject which is used by the proxy model to get the desired display value.
I'm running into problems when the object pointed to by that variable is deleted. I am handling deletion in the main model via the destroyed(QObject*) signal. Inside the slot, I search through the model looking for any items that are pointing to the object and delete the reference.
That part works fine on its own but I also have connected to the onDataChanged(...) signal of the proxy model, where I call resizeColumnsToContents() on the proxy model. This then calls the proxy's data() function. Here I check to see if the item has a pointer and, if it does, get some information from the object for display.
The result of all this becomes:
Object about to be deleted triggers destroyed(...) signal
Main model looks for any items using the deleted object and calls setData to remove the reference
Tableview catches onDataChanged signal for the proxy model and resizes columns
Proxy model's data(...) is called. It checks if the item in the main model has the object pointer and, if so, displays a value from the object. If not, it displays something else.
The problem is, at step 4 the item from the main model apparently still hasn't been deleted; the pointer address is still stored. The object the pointer was referencing, though, has been deleted by this point resulting in a segfault.
How can I fix my setup to make sure the main model is finished deleting pointer references before the proxy model tries to update?
Also, here is pseudo-code for the relevant sections:
// elsewhere
Object *someObject = new QObject();
QModelIndex index = mainModel->index(0,0);
mainModel->setData(index, someObject, ObjectPtrRole);
// do stuff
delete someObject; // Qt is actually doing this, I'm not doing it explicitly
// MainModel
void MainModel::onObjectDestroyed(QObject *obj)
{
// iterating over all model items
// if item has pointer to obj
item->setData(QVariant::fromValue(NULL), ObjectPtrRole));
}
// receives onDataChanged signal
void onProxyModelDataChanged(...)
{
ui->tblProxyView->reseizeColumnsToContents();
}
void ProxyModel::data(const QModelIndex &index, int role) const
{
QModelIndex srcIndex = mapToSource(index);
if(role == Qt::DisplayRole)
{
QVariant v = sourceModel()->data(srcIndex, ObjectPtrRole);
Object *ptr = qvariant_cast<Object*>(v);
if(ptr != NULL)
return ptr->getDisplayData();
else
return sourceModel->data(srcIndex, role);
}
}
The problem is ptr is not NULL, but the referenced object is deleted, at the time ProxyModel::data(...) is called so I end up with a segfault.
To avoid dangling pointer dereferences with instances of QObject, you can do one of two things:
Use object->deleteLater - the object will be deleted once the control returns to the event loop. Such functionality is also known as autorelease pools.
Use a QPointer. It will set itself to null upon deletion of the object, so you can check it before use.

How to make a propertysheetpage be a selection provider?

I've got a contributed command and a handler for it. The handler's execute event has to get the value for the property actually selected in the properties view and act on it, or to be disabled if no property selected.
I've tried:
1) Set the selection provider to something which provides selection from the property view. Something in this case is just PropertySheetViewer for my PropertySheetPage, but i can't set it as the selection provider because the PropertySheetPage's viewer is private and has no getter.
2) Overriding PropertySheetPage's createControl method: This method creates a Tree control for the PropertySheetViewer. A selection listener can be installed for that tree control, so maybe i can make my command handler implement SelectionListener... The solution would be somethin like:
In my editor:
public Object getAdapter(#SuppressWarnings("rawtypes") Class type) {
if (type == IPropertySheetPage.class) {
PropertySheetPage page = new PropertySheetPage() {
#Override
public void createControl(Composite parent) {
super.createControl(parent);
IHandler handler = someWayToGetMyCmdHandler();
((org.eclipse.swt.widgets.Tree) getControl())
.addSelectionListener(handler);
}
};
IPropertySheetEntry entry = new UndoablePropertySheetEntry(
getCommandStack());
page.setRootEntry(entry);
return page;
}
return super.getAdapter(type);
}
And my command handler implementing SelectionListener as i said... The problem with this approach is that i can't find a way to get a reference to my contributed command handler (someWayToGetMyCmdHandler() above).
Has anybody got any clue on this, or any other possible approach to the problem??
There's handleEntrySelection(ISelection selection) method in PropertySheetPage that you could override to be notified about selection changes in the viewer (although PropertySheetPage is #noextend).
The second part (updating the handler) is a bit more tricky than it would normally be. Commands/handlers get updated automatically when workbench selection changes (you just need to implement setEnabled(Object evaluationContext) AbstractHandler). But since PropertySheetPage is designed to change its input on global selection change, then you have to find some custom way to notify/update your handler.
As I understand, it is currently not possible to extend the platform command event handling mechanism with custom variables, so you just need to directly look up your handler using IHandlerService of the workbench.