I started implementing MVVM for one of my Silverlight applications.
(I'm not using any toolkit).
My page contains a section with two combo boxes. Selecting an item in one of these combos triggers a search that updates a grid visible below the combos.
Each combo's selected item is bound to a property in my view model. The setter of these properties raise the INotifyPropertyChanged property change and updates the data bound to the grid automatically.
Everything was fine until I needed to add a reset button which purpose is to reset the search parameters i.e.: each combo box should not indicate any item and the grid should be empty.
If the reset function in the viewmodel updates the backing fields, the UI won't reflect the changes as RaisePropertyChanged will not be called.
If the reset function in the viewmodel updates the properties, the UI will reflect the changes but the grid will be updated twice: when reseting the first property to null and also for the second
Any help appreciated
/// <summary>Selected user.</summary>
public User SelectedUser
{
get { return _selectedUser; }
set
{
_selectedUser = value;
RaisePropertyChanged("SelectedUser");
UpdateProducts();
}
}
/// <summary>Selected product category.</summary>
public ProductCategory SelectedProductCategory
{
get { return _selectedProductCategory; }
set
{
_selectedProductCategory = value;
RaisePropertyChanged("SelectedProductCategory");
UpdateProducts();
}
}
// Reset option 1
public void Reset()
{
_selectedUser = null;
_selectedProductCategory = null;
_products = null;
}
// Reset option 2
public void Reset()
{
SelectedUser = null;
SelectedProductCategory = null;
// No need to update Products which has already been updated twice...
}
This is something that really urks me in many frameworks, WPF included. What you need is some concept of delaying the response to change notifications so that the user never sees intermediate states. However, you can't change the way WPF responds to your notifications, so the best you can do is to delay your notifications until "after the dust has settled". In your case, you will want to change both of the backing fields before any notifications are sent. Your reset method can encode this idea as follows:
public void Reset()
{
_selectedUser = null;
_selectedProductCategory = null;
_products = null;
RaisePropertyChanged("SelectedUser");
RaisePropertyChanged("SelectedProductCategory");
}
In my opinion, the way WPF synchronously updates the display in response to notifications of change is just plain wrong. Their DependencyProperty system gives them an opportunity to merely mark dependencies as dirty and perform recalculation at a later time.
I use the idea of marking as dirty and asynchronous recalculation as a general solution to the problem you've noted in this question and these days I could not imagine programming without it. It's a shame that more frameworks do not work this way.
You can raise a single PropertyChanged event for all properties after you updated the backing fields:
RaisePropertyChanged(String.Empty);
If you use the backing Fields you would have to call
RaisePropertyChanged("SelectedUser");
RaisePropertyChanged("SelectedProductCategory");
in the Reset() method.
Related
I have a org.apache.wicket.markup.html.panel.FeedbackPanel in my panelA class. The feedback panel is instantiated in a panelA constructor with one message to display -> feedbackPanel.info("displayFirstTime"). I am navigating to a new page and later to the previous panelA with a command
target.getPage().get(BasePage.CONTENT_PANEL_ID).replaceWith(panelA);
but the message "displayFirstTime" won't be displayed on the feedback panel again.
I have made it with overriding a panel onBeforeRender method
#Override
public void onBeforeRender() {
super.onBeforeRender();
if (again_displayCondition) {
this.info("displayFirstTime");
}
}
but it's not a clean solution.
Is it possible or how to make it, that when moving to a panelA page for the 2nd time the feedback message will be also displayed ?
Wicket uses application.getApplicationSettings().getFeedbackMessageCleanupFilter() to delete the feedback messages at the end of the request cycle.
By default it will delete all already rendered messages.
You can setup a custom cleanup filter that may leave some of the messages, e.g. if they implement some interface. For example:
component.info(new DoNotDeleteMe("The actual message here."));
and your filter will have to check:
#Override
public boolean accept(FeedbackMessage message)
{
if (message.getMessage() instanceOf DoNotDeleteMe) {
return false;
}
return message.isRendered();
}
Make sure you implement DoNotDeleteMe#toString() so that it renders properly. Or you will have to use a custom FeedbackPanel too.
DoNotDeleteMe must implement java.io.Serializable!
I'm creating a custom Ecore editor based on the Sample Ecore editor (org.eclipse.emf.ecore* plugins) and found out that changes in lists do not manifest in model change notifications. For example, changes to the EAnnotation.references list will not result in model change notifications, whereas the EAnnotation.setSource() method creates a notification. I guess, this is one of the reasons why the default getText() method in the EAnnotationItemProvider uses only the source field.
I'm using the value of the references field to generate the UI presentation of the EAnnotation, so seeing the changes to this field would be necessary for correct operation.
Is there some standard way to observe these changes and fire a refresh() on the model views?
I figured out that the notification was created but the notifyChanged() method initially ignored it. This is what worked for me:
#Override
public void notifyChanged(Notification notification) {
updateChildren(notification);
switch (notification.getFeatureID(EAnnotation.class)) {
case EcorePackage.EANNOTATION__SOURCE:
case EcorePackage.EANNOTATION__REFERENCES: /*ADDED*/
fireNotifyChanged(new ViewerNotification(notification, notification.getNotifier(), false, true));
return;
case EcorePackage.EANNOTATION__CONTENTS:
fireNotifyChanged(new ViewerNotification(notification, notification.getNotifier(), true, false));
return;
case EcorePackage.EANNOTATION__DETAILS: /*MODIFIED TO UPDATE VIEW*/
fireNotifyChanged(new ViewerNotification(notification, notification.getNotifier(), true, true));
return;
}
super.notifyChanged(notification);
}
I am using MvvmCross for creation my Android-app and I faced with the following problem:
When I'm trying to show AlertDialog, that was created in ViewModel, the
"Unhandled Exception: Android.Views.WindowManagerBadTokenException" appears.
public class MyViewModel : MvxViewModel
{
public ICommand ShowAlertCommand { get; private set; }
public AuthorizationViewModel()
{
ShowAlertCommand = new MvxCommand(() =>
{
var adb = new AlertDialog.Builder(Application.Context);
adb.SetTitle("Title here");
adb.SetMessage("Message here");
adb.SetIcon(Resource.Drawable.Icon);
adb.SetPositiveButton("OK", (sender, args) => { /* some logic */});
adb.SetNegativeButton("Cancel", (sender, args) => { /* close alertDialog */});
adb.Create().Show();
});
}
}
When I was researching I have found that it happens because of transmission of the reference to the Context but not on the Activity in the AlertDialog.Builder.
In this topic I found the following decision:
Receive references to the current Activity through the use of GetService(), but I didn't found mvvmcross plugins for work with IMvxServiceConsumer, IMvxAndroidCurrentTopActivity interfaces.
My question is can I show AlertDialog from ViewModel? And how can I get the reference to Activity, but not to the Application.Context?
And what is the correct way to close AlertDialog that the user would stay on the current View?
In general, you should try not to put this type of code into ViewModels
because ViewModels should stay platform independent
because ViewModels should be unit testable - and it's hard to unit test when the code shows a dialog
I'd also recommend you don't put code like this inside a ViewModel Constructor - these constructors are generally called during navigations and displaying a Dialog during a transition is likely to be problematic.
With those things said, if you do want to get hold of the current top Activity within any code, then you can do this using the IMvxAndroidCurrentTopActivity
public interface IMvxAndroidCurrentTopActivity
{
Activity Activity { get; }
}
Using this, any code can get the current Activity using:
var top = Mvx.Resolve<IMvxAndroidCurrentTopActivity>();
var act = top.Activity;
if (act == null)
{
// this can happen during transitions
// - you need to be sure that this won't happen for your code
throw new MvxException("Cannot get current top activity");
}
var dlg = new AlertDialog.Builder(act);
//...
dlg.Create().Show();
The use of IMvxAndroidCurrentTopActivity is discussed in MvvmCross: How to pass Android context down to MvxCommand?
The approach taken in that question/answer is also one of the ways I would generally approach showing dialogs from a ViewModel:
I would create an IFooDialog interface
Ideally I would probably make this interface asynchronous - e.g. using async or using an Action<DialogResult> callback parameter
on each platform I would implement that in the UI project
the ViewModels can then use IFooDialog when a dialog is needed and each platform can respond with an appropriate UI action
This 'Dialog Service' type of approach is common in Mvvm - e.g. see articles like http://www.codeproject.com/Articles/36745/Showing-Dialogs-When-Using-the-MVVM-Pattern (although that article is very Windows specific!)
There are also a few other questions on here about MvvmCross and dialogs - although they may contain reference to older v1 or vNext code - e.g. Alerts or Popups in MvvmCross and Unable run ProgressDialog - BadTokenException while showind
I have to bind my editor widget objects in property sheet.So that i can the property of my widget from property view.
Please help me on this, if possible provide me some code snippets.
You have a good example in the Getting started with Properties
Using the Properties view is simple enough.
Since it shows properties for the selected object, the first step to using it is to make sure that the workbench selection service knows about the object selected in your view. There’s an entire Eclipse Corner article written on the subject of the selection service
public void createPartControl(Composite parent) {
viewer = new TableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
viewer.setContentProvider(new ViewContentProvider());
viewer.setLabelProvider(new ViewLabelProvider());
getSite().setSelectionProvider(viewer);
viewer.setInput(getViewSite());
}
Once you have your view contributing to the workbench selection, you need to make sure that the objects that your view is selecting contribute properties
(extract)
public class Person implements IPropertySource {
private String name;
private Object street;
private Object city;
public Person(String name) {
this.name = name;
this.street = "";
this.city = "";
}
public Object getEditableValue() {
return this;
}
public IPropertyDescriptor[] getPropertyDescriptors() {
return new IPropertyDescriptor[] {
new TextPropertyDescriptor("name", "Name"),
new TextPropertyDescriptor("street", "Street"),
new TextPropertyDescriptor("city", "City")
};
}
I indicated earlier that this solution is “not necessarily [the] most correct”. This is because, for this to work, my domain object needs to know about the very view-centric (and Eclipse-centric) notion of being a property source; in short, there is a tight-coupling between the model and view and this not a good thing™.
Using adapter is a better approach, as described in this article:
Person should implement IAdaptable.
See also this recent article on how to create a custom property view
how to hack the Properties View to listen only to a specific view.
The isImportant() method is the one which decides whether to create an IPage for the specific IWorkbenchPart or not.
The idea is to override that method and return false for all the workbenchPart that we are not interested in. Lets create the view first:
<view
class="com.eclipse_tips.views.CustomPropertiesView"
icon="icons/sample.gif"
id="com.eclipse-tips.views.customePropertiesView"
name="My Properties View">
</view>
The CustomPropertiesView should extend PropertySheet and override the isImportant():
public class CustomPropertiesView extends PropertySheet {
#Override
protected boolean isImportant(IWorkbenchPart part) {
if (part.getSite().getId().equals(IPageLayout.ID_PROJECT_EXPLORER))
return true;
return false;
}
}
In this case, I'm making the view only to respond to Project Explorer and ignore other views
According to this thread, the same principle should be valid for an Editor instead of a View.
The property sheet listens to the workbench page selection provider.
The selection provider depends on what viewer/editor is active.
Each editor/viewer provides their own selection provider to use when that editor/viewer is active.
This way the property sheet doesn't care who is active, it just listens to the selection provider.
That way depending upon the view, a different set of properties are displayed.
For example, the Navigator view provides IResource selections, so the property sheet then displays IResource properties when the Navigator is active.
The Workbench Selection mechanism is illustrated in this article
The ISelectionListener is a simple interface with just one method.
A typical implementation looks like this:
private ISelectionListener mylistener = new ISelectionListener() {
public void selectionChanged(IWorkbenchPart sourcepart, ISelection selection) {
if (sourcepart != MyView.this && // 1
selection instanceof IStructuredSelection) { // 2
doSomething(((IStructuredSelection) selection).toList()); // 3
}
}
};
Depending on your requirements your listener implementation probably needs to deal with the following issues as shown in the code snippet above:
In case we also provide selections (e.g. a view or editor) we should exclude our own selection events from processing. This avoids unexpected results when the user selects elements within our part (1).
Check whether we can handle this kind of selection (2).
Get the selected content from the selection and process it (3).
I am building an image Editor as an Eclipse plugin.
I would like to use the Properties view to view & edit properties of the model underneath the image. Accordingly I am calling ..
getSite().setSelectionProvider( this );
.. within createPartControl, and implementing the ISelectionProvider interface in my EditorPart implementation, so that the model is returned as the selection (which must therefore implement the ISelection interface).
The next step is for the Editor to implement IAdaptable to supply an adapter for the selected object.
My problem however is that getAdapter is never called with IPropertySource.class, and therefore the Properties View never gets what it needs to make sense of the image model.
Your help is much appreciated.
M.
The answer in the end broke down into a few pieces ...
1.) When your selection does change (if a user has zoomed into the image, for example) be sure to tell Eclipse this. It won't happen otherwise.
2.) When sending your SelectionChangedEvent, wrap up your IAdaptable in a StructuredSelection object - otherwise the Properties view will ignore it.
This boiled down to the following method
public void fireSelectionChanged()
{
final SelectionChangedEvent event = new SelectionChangedEvent( this, new StructuredSelection( this ) );
Object[] listeners = selectionChangedListeners.getListeners();
for (int i = 0; i < listeners.length; ++i)
{
final ISelectionChangedListener l = (ISelectionChangedListener) listeners[i];
SafeRunnable.run(new SafeRunnable() {
public void run() {
l.selectionChanged( event );
}
});
}
}
... on an class that implemented ISelectionProvider & IAdaptable.
M.