`
zhaozhongwei
  • 浏览: 52563 次
  • 性别: Icon_minigender_1
  • 来自: 沈阳
社区版块
存档分类
最新评论

Remember the State

阅读更多

As a rule of thumb, your application should try to remember the state across sessions. So when a user hits the close button and then opens it after few mins/days, it should present exactly in the same way where he left. In this tip, I'm going to explain few things of how this could be achieved.

The first and foremost thing is the number of windows opened and current perspective and its state in each window. If you are writing a plugin for the IDE, you need not worry about this. Eclipse does that for you. If you are an RCP app, you have to do it yourself. You have to do it in the WorkbenchAdvisor.initialize() method:

01. public class ApplicationWorkbenchAdvisor extends WorkbenchAdvisor {
02.    
03.   @Override
04.   public void initialize(IWorkbenchConfigurer configurer) {
05.  
06.    super .initialize(configurer);
07.    configurer.setSaveAndRestore( true );
08.   }
09.  
10.   // other methods ...
11. }


Now this will take care of lot of things - the number of windows that are opened; location & size of those windows; perspective of each window; view & editors in each perspective; their locations,... All by setting that one boolean variable!

Now that our views and editors are restored by Eclipse, we need to ensure the state of them. Most of the views will be tree/table based. In these cases, the current selection is an important one to restore.

To store these items and anyother stuff you want to, Eclipse provides IMemento. An instance of this IMemento is passed to the view when its initialized and when workbench is closed. The information can be stored hierarchically with string keys and it will be persisted as an XML. If you are wondering why can't it be as simple as passing a Serializable Java object and the workbench persisting it, the answer is simple. The same class may not be there when Eclipse starts next time or even if the same class is available, it might have changed. IMemento avoids this problem by persisting the stae as an XML string.

So how do we use it? In the view, you have to override the init() and saveState() methods:

01. @Override
02. public void init(IViewSite site, IMemento memento) throws PartInitException {
03.   this .memento = memento;
04.   super .init(site, memento);
05. }
06.  
07. public void createPartControl(Composite parent) {
08.          // create the viewer
09.   restoreSelection();
10. }
11.  
12. private void restoreSelection() {
13.   if (memento != null ) {
14.    IMemento storedSelection = memento.getChild( "StoredSelection" );
15.    if (storedSelection != null ) {
16.     // create a structured selection from it
17.     viewer.setSelection(selection);
18.    }
19.   }
20. }
21.  
22. @Override
23. public void saveState(IMemento memento) {
24.   IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
25.   if (!selection.isEmpty()) {
26.    IMemento storedSelection = memento.createChild( "StoredSelection" );
27.    // store the selection under storedSelection
28.   }
29. }


Not just the selection, we can store any other information (which of the tree nodes are expanded, sorter & filter settings etc) which will be useful to restore the state.

Great. Now moving on from the views, the next item would be dialogs. Similar to the workbench windows, you can store the size, location and other elements of a dialog as well. The functionality for size and location is available by default, but you need to enable it by overriding the Dialog.getDialogBoundsSettings() method. The Dialog class stores & retrieves the size and location from IDialogSettings returned from that method. The original implementation returns null, so nothing is saved. We need to create an instanceof IDialogSettings and return it. Your plugin's Activator simplifies that. When required, it creates a dialog_settings.xml under your plugin's data area and store the dialog settings of all the dialogs. You have to create a separate section for each dialog.

01. private static final String MY_DIALOG_SETTINGS = "MY_DIALOG_SETTINGS" ;
02.  
03. @Override
04. protected IDialogSettings getDialogBoundsSettings() {
05.   IDialogSettings settings = Activator.getDefault().getDialogSettings();
06.   IDialogSettings section = settings.getSection(MY_DIALOG_SETTINGS);
07.   if (section == null ) {
08.    section = settings.addNewSection(MY_DIALOG_SETTINGS);
09.   }
10.   return section;
11. }


In case you want to store only the location or size, you can specify it by overriding the getDialogBoundsStrategy() method.

Much like the IMemento, the IDialogSettings basically organizes the key-value strings in an hierarchical way. So along with the size & location, you can store any other information in this IDialogSettings. Its a good practice to store the values of the widgets (which radion button is selecte, checked state of a check box, etc) in the dialog, so its faster for an user who frequently repeats an operation.

Talking about the widgets, the trickier one is Combo. When the list of options are predefined and the user can't enter a new value, then its easy. But in places where the user can enter the values, (like File/Directory selection, search boxes), remembering them is not straight forward.

We shouldn't be storing every single value the user enters, but only the recently used ones. Probably with a limit of 5 or 10 items only. This can be done with the help of LinkedHashSet. It guarantees the order of the element, so whenever the user enters a new values, put the current value first in the set then add the rest of the elements (even if the first element is repeated, it won't change the position). Then take the first N elements and store it.

01. private static final String COMBO_STATE= "COMBO_STATE" ;
02.  
03. private static final int HISTORY_SIZE = 5 ;
04.  
05. private String []comboState;
06.  
07. private void restoreComboState(IDialogSettings settings) {
08.   comboState = settings.getArray(COMBO_STATE);
09.   if (comboState == null )
10.    comboState = new String[ 0 ];
11.   for (String value : comboState) {
12.    myCombo.add(value);
13.   }
14. }
15.  
16. private void saveComboState(IDialogSettings settings) {
17.  
18.   // use LinkedHashSet to have the recently entered ones
19.   LinkedHashSet<String> newState = new LinkedHashSet<String>();
20.   newState.add(myCombo.getText());
21.   newState.addAll(Arrays.asList(comboState));
22.  
23.   // now convert it to the array of required size
24.   int size = Math.min(HISTORY_SIZE, newState.size());
25.   String[] newStateArray = new String[size];
26.   newState.toArray(newStateArray);
27.  
28.   // store
29.   settings.put(COMBO_STATE, newStateArray);
30. }


One last piece. Look at this dialog:



Sometimes only for the first time you want to ask the question to the user. Thereafter if the user prefers, you can use the same answer. To simplify this, you can use the MessageDialogWithToggle. You need to pass the preference store and the key for the preference value. When the user selects the check box, the value will be stored. From the next time onwards, you can check the value from the preference and use it.

1. MessageDialogWithToggle.openYesNoQuestion(shell, "Remember me" , "Is this tip useful?" ,
2.    "Don't bug me with this question again" , true ,
3.    Activator.getDefault().getPreferenceStore(), "BUG_USER_KEY" );


There you go:


:-)

 

 

 

 

 

 

 

 

Keyboard accessibility thru Command Framework

Keyboard shortcuts is usually much speedier than reaching out your mouse, moving it, pointing it to something and clicking. But there are some things which cannot be done that easily by keyboard shortcuts. For me, one of them is finding out a closed project and open it. Unfortunately, I've quite a large set of projects in my workspace and try to keep most them closed, when not used. In addition to that in the Package Explorer, I've the 'Closed Projects' filter on. So if I need to open a project, I've use the pull down menu, uncheck 'Closed Projects' navigate thru the working sets to find the right project and double click it. To enable keyboard access to this regular task, I decided to make use of Commands Framework .

The solution is to add a parameterized command , and in the values, I compute the projects which are closed. So when I press the awesome shortcut (Ctrl+3) it would display me the list of closed projects. With few keys, I can navigate to the project I want and open it. Lets see how to do it. First step is the command with the parameter:

01. < extension point = "org.eclipse.ui.commands" >
02.     < command
03.              defaultHandler = "com.eclipse_tips.handlers.OpenProjectHandler"
04.              id = "com.eclipse-tips.openProject.command"
05.              name = "Open Project" >
06.        < commandParameter
07.                 id = "com.eclipse-tips.openProject.projectNameParameter"
08.                 name = "Name"
09.                 optional = "false"
10.                 values = "com.eclipse_tips.handlers.ProjectNameParameterValues" >
11.        </ commandParameter >
12.     </ command >
13. </ extension >


And the handler:

01. public Object execute(ExecutionEvent event) throws ExecutionException {
02.   String projectName = event.getParameter( "com.eclipse-tips.openProject.projectNameParameter" );
03.   IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
04.   IProject project = root.getProject(projectName);
05.   try {
06.    project.open( null );
07.   } catch (CoreException e) {
08.    throw new ExecutionException( "Error occured while open project" , e);
09.   }
10.   return null ;
11. }


For the parameter values, I look for closed projects and return them:

01. public Map<String, String> getParameterValues() {
02.  
03.   IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
04.   IProject[] projects = root.getProjects();
05.   Map<String, String> paramValues = new HashMap<String, String>();
06.   for (IProject project : projects) {
07.    if (project.exists() && !project.isOpen()) {
08.     paramValues.put(project.getName(), project.getName());
09.    }
10.  
11.   }
12.   return paramValues;
13. }



So finally, When I press Ctrl+3 and type OPN, I get the list:

 


This idea can be extended to provide keyboard accessibility to many functionalities. Say in an RCP mail application, you can add a command like 'Go To Mail' with parameter as the Subject/Sender:


Hmmm, if only the 'Built On Eclipse' mail app that I *have* to use, knows the existence of threads other than the UI thread :-(

 

 

 

 

 

 

 

Subtask in ProgressMonitors

Here is a trivial tip. When inspecting a bug, I found a interesting thing on Progress Monitors. We know monitor.worked() increments the progress bar, but how do we change the text to update the current subtask? The initial text is set by the beginTask() method and it should be called only once. I digged into the IProgressMonitor and found the subtask() method:

01. IRunnableWithProgress operation = new IRunnableWithProgress() {
02.  
03.   public void run(IProgressMonitor monitor) {
04.  
05.    monitor.beginTask( "Main task running ..." , 5 );
06.    for ( int i = 0 ; i < 5 ; i++) {
07.     monitor.subTask( "Subtask # " + i + " running." );
08.     runSubTask( new SubProgressMonitor(monitor, 1 ), i);
09.    }
10.   }
11.  
12. };

 


Now the question is what happens when the runSubTask() method sets another subTask on the SubProgressMonitor?

01. private void runSubTask(IProgressMonitor monitor, int subTaskId) {
02.  
03.   monitor.beginTask( "Sub task running" , 10 );
04.    for ( int i = 0 ; i < 10 ; i++) {
05.     monitor.subTask( "Inside subtask, " + i + " out of 10" );
06.     // do something here ...
07.     monitor.worked( 1 );
08.     if (monitor.isCanceled())
09.      throw new OperationCanceledException();
10.   }
11.    monitor.done();
12.   }
13.  
14. }


Basically the SubProgressMonitor's subTask() overwrites the parent's subTask(). Thats the default behaviour. You can customize it with the style bits provided in the SubProgressMonitor:

If you want to append the SubProgressMonitor's subTask info, use the style PREPEND_MAIN_LABEL_TO_SUBTASK:

1. new SubProgressMonitor(monitor, 1 , SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK), i);

 


Else if you want to ignore it altogher then use the SUPPRESS_SUBTASK_LABEL style:

 

 

1. new SubProgressMonitor(monitor, 1 , SubProgressMonitor.SUPPRESS_SUBTASK_LABEL), i);



In a previous tip, we saw how to make a Properties View to respond only to a particular View or Editor. In this tip, we are going to see how to exclude a particular View or Editor from the Properties View. This will be very helpful in RCP apps, where they don't want the generic view like Outline view or an editor that contributes to the Properties View. Last time we extended the Properties View to create our own view and added some code. But this time its simple. You just have to use the org.eclipse.ui.propertiesView extension point.

<extension
         point="org.eclipse.ui.propertiesView">
      <excludeSources
            id="org.eclipse.ui.views.ContentOutline">
      </excludeSources>
      <excludeSources
            id="com.eclipse_tips.editors.MyOwnEditor">
      </excludeSources>
</extension>

Yes, its that simple :-) Don't try with Galileo, you would need Helios (3.6) M2 to get this working.
分享到:
评论

相关推荐

    Android代码-flow

    Flow "Name-giving will be the foundation of our science." — Linnaeus "The winds and waves are always on the side of the ablest ...Remember the UI state, and its history, as you navigate and acros

    Mastering+Python+Networking-Packt+Publishing(2017).pdf

    network engineer, or developer might not remember the exact TCP state machine on a daily basis (I know I don't), but he/she would be familiar with the basics of the OSI model, the TCP and UDP ...

    concurrent-patterns-best-practices-synchronization

    I still remember the Aha! moment when I understood how UNIX shell pipeline works. I fell headlong in love with Linux and the command line and tried out many combination filters (a filter is a program ...

    The Art of Assembly Language Programming

    An Easy Way to Remember the 8086 Memory Addressing Modes 4.6.2.8 - Some Final Comments About 8086 Addressing Modes 4.6.3 - 80386 Register Addressing Modes 4.6.4 - 80386 Memory Addressing ...

    web mappingn illustrated

    With the help of the ...Remember the fun you had exploring the world with maps? Experience the fun again with Web Mapping Illustrated. This book will take you on a direct route to creating valuable

    外文翻译 stus MVC

    Do you remember the days of function mappings? You would map some input event to a pointer to a function. If you where slick, you would place the configuration information into a file and load the ...

    pro spring 2.5

    The Spring Framework 2.5 release reflects the state of the art in both the Spring Framework and in enterprise Java frameworks as a whole. A guide book to this critical tool is necessary reading for ...

    端口查看工具

    o New Option: Remember Last Filter (The filter is saved in cports_filter.txt) * Version 1.33: o Added support for saving comma-delimited (.csv) files. o Added new command-line option: /scomma * ...

    HOW TO IMPROVE YOUR BUSINESS LETTERS

    1. Remember the old public speaking adage: "Tell them what you're going to say, say it, then tell them what you said." The same principle holds when writing a business report. In an introductory ...

    Google C++ Style Guide(Google C++编程规范)高清PDF

    Header Files The #define Guard Header File Dependencies Inline Functions The -inl.h Files Function Parameter Ordering Names and Order of Includes Scoping Namespaces Nested Classes Nonmember, Static ...

    iOS-2048-master

    If you do make improvements to it, remember to put yourself in the "About 2048" page to get yourself credit. If you'd like to fork and make your own version, that's also great! Feel free to tinker ...

    微软内部资料-SQL性能优化2

    The diagram included at the top represents the address partitioning for the 32-bit version of Windows 2000. Typically, the process address space is evenly divided into two 2-GB regions. Each process...

    ft8pe53.pdf

    The easy to use and easy to remember instruction set reduces development time significantly. The FM8PE53 series consists of Power-on Reset (POR), Brown-out Reset (BOR), Power-up Reset Timer (PWRT), ...

    在线优化监控用的感应器调度函数

    In supervisory control, the objective of observation is to guarantee a ... Applying the extended result, the online algorithm only needs to look one step ahead and remember the current state estimate.

    微软内部资料-SQL性能优化5

    The only source of any storage location information is the sysindexes table, which keeps track of the address of the root page for every index, and the first IAM page for the index or table....

    Sakemail

    Some minor bugs that I don‘t remember fixed.- Added MIME-compliant base64 support (not for use by now). Added examples.0.9.2.1b- Fixed a bug when send a mail and the first line disappear (thanks to ...

    ViewPager 放大缩小左右移动

    * Called when the scroll state changes. Useful for discovering when the * user begins dragging, when the pager is automatically settling to the * current page, or when it is fully stopped/...

    CompTIA.Linux.LPIC-1.Cert.Guide.07897545

    Two full sample exams offer realistic practice delivered through Pearson's state-of-the-art PCPT test engine Table of Contents Chapter 1 Installing Linux Chapter 2 Boot Process and Runlevels Chapter 3...

    Android代码-一个支持定制的树状 Android 自定义View

    ②Remember expansion state Automatically expand the last unclosed directory ③Customize TreeAdapter Different types of documents show different Icon ④Dynamic add and delete nodes refresh status ...

    sedemosedemo

    your programs, except to state that your program was written using this Almediadev' software. PROVISIONS FOR VISUAL COMPONENT LIBRARY CLASSES =============================================== ...

Global site tag (gtag.js) - Google Analytics