SIP prevents EF from auto-detecting object change on WP7 - entity-framework

I have an issue trying to persist entities to the DB using code-first technique. For example of what I am doing you may look at the following MSDN Sample. The app generally works as intended except for one case.
If I have an existing entity and I bind it to a page that has a TextBox to hold the Title field and a AppBar icon to Save (similar to the 'New Task' screenshot in the above link, but with values pre-filled with existing entity with Two-Way binding), the following issue occurs. If I have the TextBox selected and I change the title and hit the save button, it updates the entity in-memory so that the full list now displays the new title. But the new title is not persisted to the DB (it does not auto-detect changes). This is weird, not just because the object in-memory has changed, but also because if I deselect the TextBox and then hit save, it will persist the changes to the DB.
I have seen other questions on SO with some change detection issues, they suggest adding this.Focus() or focusing some other element at the beginning of the save method. This does not help in my case. Unless I tap on screen to deselect the TextBox and hide the keyboard (or press Return key on the keyboard, which I bound to do this.Focus()), it won't detect the object as changed.
How can I address this? What exactly is stopping EF from detecting the object change when the keyboard is still visible?

Not sure if I follow exactly what you described but I think the problem is that the property you have bound your textbox to does not get updated until the TextChanged is fired on the textbox, and this is done first when you leave the Textbox, basically it will lose Focus if you tap somewhere else.
There is a simple workaround for this and it behaviors. By making a small behavior you can force the textbox to update the binding on each keystroke - so everything is updated while you type and keyboard is still there.
Behavior:
/// <summary>
/// Update property on every keystroke in a textbox
/// </summary>
public class UpdateTextSourceTriggerBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
this.AssociatedObject.TextChanged += OnTextBoxTextChanged;
}
void OnTextBoxTextChanged(object sender, TextChangedEventArgs e)
{
var bindingExpression = AssociatedObject.ReadLocalValue(TextBox.TextProperty) as BindingExpression;
if (bindingExpression != null)
{
bindingExpression.UpdateSource();
}
}
protected override void OnDetaching()
{
this.AssociatedObject.TextChanged -= OnTextBoxTextChanged;
}
}
Now just attach this behavior to your textbox like this:
<TextBox Text="{Binding YourPropertyName, Mode=TwoWay}">
<i:Interaction.Behaviors>
<UpdateTextSourceTriggerBehavior/>
</i:Interaction.Behaviors>
</TextBox>
This will keep the property on your viewmodel updated all the time, so that when you tap on save directly after typing in the textbox it will save the correct value. Hope it helps!
Cheers,
Anders

Related

MAUI with MVVM - How Do I implement a Submit method in a 'details' page?

I am new to MVVM and MAUI, and I am quite a bit confused now.
This is the situation - I will explain it as well as I can:
In my main page I have a CollectionView with frames etc., and in each frame I show a picture of some CAR object. On clicking on a frame I execute the command
[RelayCommand]
private async Task GoToCarsDetails(Car car)
{
if (car == null)
return;
await Shell.Current.GoToAsync(nameof(CarPage), true, new Dictionary<string, object>
{
{"Car", car}
});
}
In my CarPage.xaml, which I reach with the method above, I have a grid which contains this xaml:
<Label
x:Name="Description"
Grid.Row="2"
Style="{StaticResource MediumLabelCentered}"
Text="{Binding Car.Description, StringFormat='Description: {0}'}" />
<Label
x:Name="Color"
Grid.Row="3"
Style="{StaticResource MediumLabelCentered}"
Text="{Binding Car.Color, StringFormat='Color: {0}'}" />
and also an Editor to be able to change the Description, a Picker to be able to change the Color, and a Submit button.
I want to allow the user to set some description and a color, and this should NOT immediately change the CAR object passed as param to the method Shell.Current.GoToAsync - they should only be changed when clicking on the submit button - if the user changes for instance the color from the initial Red to Blue, but does NOT click the Submit button and goes back to the previous screen (Main) instead, the property Color should NOT have changed - it should still show "Red".
So, for instance, let's say that for some car I have the
Color = RED, and
Description = "My Car"
Click on that car, go to the CarPage.xaml, see in the Picker the color set to RED and the description in the editor = "My Car", change those to BLUE and "Your Car", respectively, and navigate away without clicking on the Submit button. The initial car should still show
Color = RED, and
Description = "My Car"
(no change).
Do the same thing as above but this time click on the submit button. This should take us to the previous page (Main), and see that the initial car now shows
Description = "Your Car", and
Color = BLUE
My problem is this:
If I BIND the controls, as seen in the xaml above, then any change on those Editor and Picker will already change the underlying Car object, and then the Submit button has no meaning anymore. If I click on it or not, that makes no difference - the Car object already contains those changes.
If instead of opening the CarPage.xaml page with the Car object I open it with a clone, with the intention of allowing the user wo freely play on the clone object, by passing a clone object
car.Clone<Car>()
(I have created an extension method for that). I can now indeed change the Clone's properties all I want, but then how do I replace on Submit the object CAR with the object CARCLONE ? The code for that Submit button exists either in the code-behind of the CarPage.xaml object, either in the CarViewModel class. In neither object do I have access to both CAR and CARCLONE, to be able to switch them.
I cannot switch these two objects (the original CAR object with the changed CARCLONE object) in the MainViewModel's GoToCarsDetails method
private async Task GoToCarsDetails(Car car)
either, because this method calls Shell.Current.GoToAsync in an async way, and I cannot add the code which would switch objects after the Shell.Current.GoToAsync call, because the code will not wait for the CarPage to be closed.
I also thought of passing the real CAR object as param, NOT binding the two labels above, play with the controls, and populate the CAR object with the updated properties only in Submit_Click().... But how do I set, on opening the CarPage page, the Text of the two labels ? (Is there a Load() event ? I couldn't find one). Writing such code in the constructor does not work (it gives me a null reference).
So... I don't know how do do this seemingly very simple task:
Pass an object to a Details form, allow it to be changed, but actually update it to our collection in Main only if the user has clicked on the submit button in that Details page (CarPage, that is), all by not violating the MVVM ideas.
Thank you very much.
Alex
There is a standard mechanism, in any language, for dealing with situations like this: a "callback".
Think about "Responsibilities". The main page (and its viewmodel) is responsible for managing the Car objects.
You want somewhere else to work with a Car object, but that "somewhere else" [I'll call it CarEditor] isn't the place that should deal with the consequences of modifying a Car.
So what do you want to pass in? A Car. What do you want to do on Submit_Click? Something that CarEditor doesn't need to know about. It should hand all the information "back" (via the callback action) to somewhere that has the responsibility to act on the result.
As Jason suggested, one way to do this is to have a clone of Car. Then pass back both original and clone. This could be done as (Car original, Car clone).
Calling code:
Car original = ...;
Editor.EditThis(original, ProcessTheEdit);
...
void ProcessTheEdit(Car original, Car clone)
{
}
Editor code:
public delegate void CallbackDlg(Car original, Car clone);
public void EditThis(Car original, CallbackDlg callback)
{
Car clone = new Car();
//... do work.
callback(original, clone);
}
I have followed your advice, and here it is:
[RelayCommand]
private async Task GoToCarsDetails(Car car)
{
Car carClone = car.Clone();
await Shell.Current.GoToAsync(nameof(CarPage), true, new Dictionary<string, object>
{
{"Car", car},
{"CarClone", carClone}
});
}
CarPage has the controls bound to CarClone.
On the Submit button in the ViewModel I have:
[RelayCommand]
private void Submit()
{
Car = carClone.Clone();
Shell.Current.GoToAsync("../", true);
}
If I change the color from the initial RED to YELLOW and I set a breakpoint on the Shell line, I see that indeed Car now has the color property = YELLOW.
Good.
However, if I click again on the same car, the CarPage view opens again with Color = RED, which is not correct. I have changed that Car object to have the color set to Yellow, right ? The Cars collection in this MainViewModel shows as unchanged !
I don't understand - I have passed the Car object to the "Details" CarView page and I have changed it from Red to Yellow, and yet... it is still Red. It's like I have passed the Car object Byval and not Byref.... :-(
Thank you,
Alex

Dynamically reload a table in a form programatically

I have a form in Dynamics AX which displays a table of two columns in a grid. I also have a button on the form. I am overriding the clicked method of the button to update the Address field of the table. For example, here's my X++ code:
void clicked()
{
AddressTable addr;
ttsBegin;
select forUpdate addr where addr.addressID == 1;
addr.Address = "new address";
addr.update();
ttsCommit;
super();
// reload table here
}
What I would like to do is to add a code to the clicked function which will reload (re-select) the updated records and show them in the form without a need of reopening the window or refreshing it using F5 (for example).
I went through forums and AX documentation and found a few methods like refresh and reread, but they are FormDataSource class methods and I failed to make it happen inside the clicked handler above.
So, what I really want to accomplish programaticallyis what F5 does behind the scenes when clicked on an open form.
Maybe just addressTable_ds.research(true); will do the job.
See also Refresh Issue on the form when the dialog closes.

EffectQueue and chaining effects in IceFaces 1.8

I'm currently working with IceFaces 1.8, and have been trying to find an simple way to chain effects on UI Components. For example, I have a "Show Help" link at the top right of the page. When clicked, help text will appear below certain controls for users. I'd like this text to appear by sliding down, then highlighting.
I have a basic isRenderHelp() method on my bean that returns true or false, and use that to render effects using the fire attribute on the <ice:effect> tag, so it looks something like this:
<ice:effect effectType="slidedown" fire="#{myBean.renderHelp}">
<ice:effect effectType="slideup" fire="#{!myBean.renderHelp}">
This works causing the help section to slide in and out as the help link toggles the renderHelp flag in the bean. There is the small exception that renderHelp returns null before the link is clicked for the first time to prevent the slideup animation from firing on the first page render.
Now, I noticed looking through the showcase code for 1.8 that there is an EffectQueue class that extends Effect. This allows me to add mutliple Effects to the queue in my bean, and return the queue from a getEffect method that I can then assign to a panelGroup effect attribute. However, it does not execute the events in the queue, despite having their priorities set. I'm sure I'm not using it properly, and I'm wondering how it should be used.
Normally I'd use jQuery for this type of thing, but the UI uses many partial submits. Our page is displayed via a Liferay portlet, so on any partialSubmit the view is rerendered, undoing any modifications to the DOM by jQuery.
So is there any simple way to chain effects in IceFaces 1.8? Suggestions?
here is how I implemented the effectQueue to appear and fade the text.
private EffectQueue effectQueue;
public Effect getSaveSettingsEffect() {
return effectQueue;
}
public void fireEffect(ActionListener e) {
if(effectQueue == null) {
effectQueue = new EffectQueue("apperAndFade");
Appear appear = new Appear();
appear.setDuration(2);
effectQueue.add(appear);
Fade fade = new Fade();
fade.setDuration(3);
effectQueue.add(fade);
effectQueue.setTransitory(true);
}
effectQueue.setFired(false);
}
facelet:
<ice:commandButton value="fireEffect" action="#{bean.fireEffect}"/>
<ice:outputText value="text" style="display: none;" effect="#{bean.effectQueue}"/>

Change the order of event handling in GWT

I have a Suggest box which has 2 hadlers: SelectionHandler for selecting items in SuggestionList and keyDownHandler on TextBox of SuggestBox. I want to prevent default action on event(for example on Enter pressed) when the suggestion list is currently showing. The problem is that SelectionEvent is always fires before KeyDownEvent and suggestion list is closed after SuggestionEvent fired, so in KeyDownEventHandler suggestion list is already closed. And I can't use prevent default action on Enter with checking the suggestion list is showing like this:
if ((nativeCode == KeyCodes.KEY_TAB || nativeCode == KeyCodes.KEY_ENTER) && display.isSuggestionListShowing()) {
event.preventDefault();
}
where display.isSuggestionListShowing() is the method which calls isShowing on SuggestBox .
So how can i change the order of event handling(Selection before KeyDown to the keyDown before Selection) in this case?
I'm assuming you mean SuggestBox instead of SuggestionList, as there is no class by that name in the gwt-user jar.
The SuggestBox uses the keydown event to provide the SelectEvent - if it can't see the keys change (from the browser, which actually picks up the user's action), it can't provide the logical selection event.
This means that reordering events doesn't really make sense - you can't have the effect before the cause. In many cases, the browser emits events in a certain order, and there is no way to change this, so you have to think differently about the problem.
(Also worth pointing out that preventDefault() only prevents the browser from doing its default behavior - other handlers will still fire as normal.)
One option would be to preview all events before they get to the SuggestBox, and cancel the event in certain cases - look into com.google.gwt.user.client.Event.addNativePreviewHandler(NativePreviewHandler) for how this can be done.
I'm not seeing any other option right away - all of the actual logic for handling the keydown is wrapped up in the inner class in the private method of com.google.gwt.user.client.ui.SuggestBox.addEventsToTextBox(), leaving no options for overriding it.

strange RaisePropertyChanged behaviour

I have a question regarding MVVM for WP7. In my application I have a kind of a datepicker or a calendar if you will. So after picking a day, one can choose to edit the information regarding this day by ckicking on it and getting to another page.
The problem is that when I change some info and click the back button the information that was altered hasn't been updating in the correspongind bindings. That's why on the navigatedto event I call a public method from my VM for that page. The sole purpose of this method (RefreshSelectedDay) is to call RaisePropertyChanged so that the binded text fields on the page get the new info. However, nothing happens. The info is actaully properties of the SelectedDay property whish is an instance of MyDay class.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
ViewModel.ViewModelLocator.CalendarStatic.RefreshSelectedDay();
}
/// <summary>
/// I admit that this is by far the most stupid solution so far.
/// RaisePropertyChanged won't work if you haven't really changed the property.
/// That's why we set it to null and then turn it back.
/// Used for updating the day when returning from
/// the edit screen.
/// </summary>
public void RefreshSelectedDay()
{
MyDay w = selectedDay;
SelectedDay = null;
SelectedDay = w;
}
My solution as you can see is far from being elegant. What happens IMO is that if I just call the RaisePropertyChanged it won't propagate because it is the same variable and just one of it's properties has changed. It would be lovely if someone can explain what is happening.
"The problem is that when I change some info and click the back button the information that was altered hasn't been updating in the correspongind bindings."
That implies to me that the information that supposedly was altered, wasn't actually altered. That would also explain why youre RefreshSelectedDay doesn't seem to work either.
Are you modifying properties on SelectedDay, or creating a new instance, and that isn't getting set?
if you're modifying properties on SelectedDay, does that class properly fire property change notifications?
More code would help us debug, too.