TableViewer with EMF databinding and cell editing - close but not quite - eclipse-plugin

I am going through Tom Shindl's instructions on how to add EMF databinding on to tables, here is my code for the data binding:
protected DataBindingContext initDataBindings() {
//going to use this person instead
Person p = ProjectFactory.eINSTANCE.createPerson();
p.setFirstName("tony");
Committership c = ProjectFactory.eINSTANCE.createCommittership();
c.setName("HELP");
Committership anotherC = ProjectFactory.eINSTANCE.createCommittership();
anotherC.setName("PELASE");
Committership yetAnotherC = ProjectFactory.eINSTANCE.createCommittership();
yetAnotherC.setName("EMERGENCY");
p.getCommittership().add(c);
p.getCommittership().add(anotherC);
p.getCommittership().add(yetAnotherC);
CommandStack cs = new BasicCommandStack();
AdapterFactory af = new ProjectItemProviderAdapterFactory();
EditingDomain editingDomain = new AdapterFactoryEditingDomain(af, cs);
//data binding context
DataBindingContext bindingContext = new DataBindingContext();
//
ObservableListContentProvider listContentProvider = new ObservableListContentProvider();
IObservableMap[] attributeMap = new IObservableMap[1];
attributeMap[0] = EMFEditProperties.value(
editingDomain,
FeaturePath.fromList(ProjectPackage.Literals.COMMITTERSHIP__NAME)
).observeDetail(listContentProvider.getKnownElements());
TableViewerColumn column = new TableViewerColumn(tableViewer, SWT.NONE);
column.getColumn().setText("First Name");
column.getColumn().setWidth(200);
column.setLabelProvider(new GenericMapCellLabelProvider("{0}", attributeMap));
//tableViewer.setLabelProvider(new ObservableMapLabelProvider(attributeMap)); -- no need for this anymore
tableViewer.setContentProvider(listContentProvider);
//instead of giving it this list and doing it the non-EMF way
IObservableList selfList = Properties.selfList(Person.class).observe(p.getCommittership());
//property that you are looking for
IListProperty prop = EMFEditProperties.list(editingDomain, ProjectPackage.Literals.PERSON__COMMITTERSHIP);
IObservableValue master = EMFEditProperties.value(editingDomain, ProjectPackage.Literals.COMMITTERSHIP__NAME)
.observe(p);
/**this should not be returning null, instead it should be a
* list of the values from the person committership EList
*/
IObservableList someList = prop.observeDetail(master);
//set input requires and IObservableList!!
tableViewer.setInput(someList);
//
return bindingContext;
}
ok, now just to talk through what is happening and where I am stuck.
this line here would work for JFace data binding:
IObservableList selfList = Properties.selfList(Person.class).observe(p.getCommittership());
it populates the table happily, it is a list containing the three people I added, nice.
now making it work with EMF databinding, I am trying this:
//property that you are looking for
IListProperty prop = EMFEditProperties.list(editingDomain, ProjectPackage.Literals.PERSON__COMMITTERSHIP);
IObservableValue master = EMFEditProperties.value(editingDomain, ProjectPackage.Literals.COMMITTERSHIP__NAME)
.observe(p);
/**this should not be returning null, instead it should be a
* list of the values from the person committership EList
*/
IObservableList someList = prop.observeDetail(master);
the problem is that someList is empty and hence he table won't populate, could someone explain why?
It is definitely those three line that have some logic problem in there.
What I really want is an IObservableList of observed EMF objects...
help would be really appreciated, since Shindl's tutorial doesn't explain where he go the master from...I thought I would create a master:
IObservableValue master = EMFEditProperties.value(editingDomain, ProjectPackage.Literals.COMMITTERSHIP__NAME)
.observe(p);
and do prop.observeDetail(master)
but it is returning an empty list as I mentioned above...if only I could at least get it do display the data, the closest I have come is having three cells but not data in them.

IObservableList listObservable = prop.observe(p);
tableViewer.setInput(listObservable);
That fixed it for me in terms of viewing the data.
As for editing the cells, I did this in the end:
public class CommittershipNameEditingSupport extends EditingSupport {
private static TableViewer tableViewer;
private final CellEditor editor;
public CommittershipNameEditingSupport(TableViewer tableViewer) {
super(tableViewer);
this.tableViewer = tableViewer;
this.editor = new TextCellEditor(tableViewer.getTable());
}
#Override
protected CellEditor getCellEditor(Object element) {
return editor;
}
#Override
protected boolean canEdit(Object element) {
return true;
}
#Override
protected Object getValue(Object element) {
return ((Committership) element).getName();
}
#Override
protected void setValue(Object element, Object value) {
((Committership) element).setName(String.valueOf(value));
tableViewer.update(element, null);
}
}
and in the main view where I craeted the column I just added the method for cell editing support...it works nicely now :)

Related

Xamarin.Android how to remember the position of items in a recyclerview

I have a recyclerview set up in xamarin.android as per the code in this link
https://www.appliedcodelog.com/2019/08/reorder-list-items-by-drag-and-drop-in.html
My question is, how can I remember the position of these items when the app is restarted etc. When the user adds items they are inserted at adapter position 0,1,2,3 etc but when they close the app and come back in, it is not always in the same order.
The user can also rearrange by drag and drop so this seems to add even more confusion!
Currently I have the items in the recyclerview being saved by converting the list to Json and loading when the app opens again but as I said, the items aren't always in the same order as before the app was closed.
Can anyone advise the best way to do this? I have tried to add the item name and position number to a list converting to json then trying to insert the item at the saved position index but can't get it to work..
Thanks
Do you want to achieve the result like following GIF?
You can use PreferenceManager to store position of items(Before store data, I will Serialize data) in a recyclerview.
You can override OnPause() method, this method will be executed when application is background or app is killed. So we can store the position and data in this method.Here is code about ReOrderActivity
[Activity(Label = "ReOrderList")]
public class ReOrderActivity : Activity, IOnStartDragListener
{
private ItemTouchHelper _mItemTouchHelper;
public static ObservableCollection<string> ResourceList;
private RecyclerView _resourceReorderRecyclerView;
ReOrderAdapters resourceAdapter;
ISharedPreferences prefs;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.ReOrderLayout);
prefs = PreferenceManager.GetDefaultSharedPreferences(this);
GetCollection();
resourceAdapter = new ReOrderAdapters(ResourceList, this);
// Initialize the recycler view.
_resourceReorderRecyclerView = FindViewById<RecyclerView>(Resource.Id.ResourceReorderRecyclerView);
Button mDone = FindViewById<Button>(Resource.Id.mDone);
mDone.Click += MDone_Click;
_resourceReorderRecyclerView.SetLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.Vertical, false));
_resourceReorderRecyclerView.SetAdapter(resourceAdapter);
_resourceReorderRecyclerView.HasFixedSize = true;
ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(resourceAdapter);
_mItemTouchHelper = new ItemTouchHelper(callback);
_mItemTouchHelper.AttachToRecyclerView(_resourceReorderRecyclerView);
}
protected override void OnPause()
{
base.OnPause();
string ConvertData = JsonConvert.SerializeObject(ResourceList);
ISharedPreferencesEditor editor = prefs.Edit();
editor.PutString("ObservableCollection_ConvertData", ConvertData);
// editor.Commit(); // applies changes synchronously on older APIs
editor.Apply(); // applies changes asynchronously on newer APIs
}
private void MDone_Click(object sender, System.EventArgs e)
{
resourceAdapter.AddItem("Add item");
}
public void OnStartDrag(RecyclerView.ViewHolder viewHolder)
{
_mItemTouchHelper.StartDrag(viewHolder);
}
//Added sample data record here
public void GetCollection()
{
//ISharedPreferencesEditor editor = prefs.Edit();
//editor.PutString("ObservableCollection_ConvertData", "");
//editor.Apply();
string ConvertData = prefs.GetString("ObservableCollection_ConvertData","");
if(string.IsNullOrEmpty(ConvertData))
{
ResourceList = new ObservableCollection<string>();
ResourceList.Add("OnPause()");
ResourceList.Add("OnStart()");
ResourceList.Add("OnCreate()");
}
else
{
ResourceList= JsonConvert.DeserializeObject<ObservableCollection<string>>(ConvertData);
}
//var or= ResourceList.ToString();
}
}
}
You can download my demo
https://drive.google.com/file/d/1mQTKf3rlcIVnf2N97amrqtnrSCRk-8ZW/view?usp=sharing

FastObjectListView UpdateObject() randomly reorders rows within primary sort

Data is a generic List of domain objects.
I click the "Deploy Status" column header to sort on that column.
I have a button that does nothing more than folv.UpdateObject(someObject) .
Every time I press that button, the Deploy Status column maintains its sort, but all rows within the sorted blocks are randomly reordered, as per screenshot.
I have commented out everything in the form's code beyond loading the data, the test button, and the FastObjectListView's column.Add() and .SetObjects(). There are no event handlers wired up for the FastObjectListView. I am not setting PrimarySort or SecondarySort in code; only by clicking with the mouse.
You should be able to fix this problem by either calling Sort after your button's call to UpdateObject or changing your usage of UpdateObject to RefreshObject
Reproducing the problem (C# Repro for the issue in the API)
This seems to reproduce the problem you are having. Run the code, sort the Other column ascending. Click the update button.
public class MainForm : Form
{
public MainForm()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
//
// MainForm
//
this.ClientSize = new System.Drawing.Size(300, 300);
this.Name = "MainForm";
this.ResumeLayout(false);
this.PerformLayout();
var OLVa = new FastObjectListView();
OLVa.Width = 250;
OLVa.Height = 250;
OLVa.Columns.Add(new OLVColumn("ID", "ID"));
OLVa.Columns.Add(new OLVColumn("Other", "Other"));
var l1 = new lolz(1, 3);
OLVa.AddObject(l1);
OLVa.AddObject(new lolz(2,3));
this.Controls.Add(OLVa);
var btn = new Button()
{
Text = "Update",
Top = OLVa.Bottom
};
btn.Click += (s,e)=>OLVa.UpdateObject(l1);
this.Controls.Add(btn);
}
private class lolz
{
public int ID;
public int Other;
public lolz(int id, int other)
{
ID = id;
Other = other;
}
}
}
Fixing the problem
The following would fix it for the above example:
btn.Click += (s,e)=>
{
OLVa.BeginUpdate();
try
{
OLVa.UpdateObject(l1);
OLVa.Sort();
}
finally
{
OLVa.EndUpdate();
}
};

Is it somehow possible to have two master views and one detail view?

If I have for example one master view on the left and one in the middle, each showing oder Java Beans/POJOs, can I use a shared detail view that somehow listens to the active beans of each view and then displays the currently selected one in more detail? A one to one relation is quite easy to manage by using your Context library.
#ViewDocking(areaId ="left", position=1, displayName="Profiles", menuEntry = #WindowMenuEntry(path = "", position=0), accelerator="Shortcut+1")
public class ProfileListView extends BorderPane implements LocalContextProvider {
private final SimpleContextContent content = new SimpleContextContent();
private final SimpleContext context = new SimpleContext(content);
#FXML
private ListView<Profile> listview;
public ProfileListView() {
load();
// add some profiles
listview.getItems().add(new Profile("Profile1"));
listview.getItems().add(new Profile("Profile2"));
listview.getItems().add(new Profile("Profile3"));
// setup selection listener
listview.getSelectionModel().selectedItemProperty().addListener((value, oldProfile, newProfile) -> {
// set active profile and remove old one
content.remove(oldProfile);
content.add(newProfile);
});
// setup double click listener
configureClickListener();
}
private Profile getSelectedProfile() {
return listview.getSelectionModel().getSelectedItem();
}
private void configureClickListener() {
listview.setOnMouseClicked(event -> {
// check if it was a double click
if(event.getClickCount() == 2) {
System.out.println(getSelectedProfile());
// inject into editor pane
// calls the procedure to create a tab in the center area...
}
});
}
private void load() {
FXMLLoaders.loadRoot(this);
}
#Override
public Context getLocalContext() {
return context;
}
}
This is one master view holding a list view of items.
The other one would be the same, docking to the right as another tab and holding POJOs of type 'Action'.
The detail view is here:
#ViewDocking(areaId = "right", displayName = "Properties", accelerator = "Shortcut+2", menuEntry = #WindowMenuEntry(path = "", position = 0), position = 1)
public class ProfilePropertiesView extends BorderPane implements LocalContextProvider, ActiveContextSensitive {
private Context activeContext;
private SimpleContextContent content = new SimpleContextContent();
private SimpleContext context = new SimpleContext(content);
private Profile profile;
private IWindowService service = new NullWindowService();
#FXML
private PropertySheet propertysheet;
public ProfilePropertiesView() {
load();
// retrieve framework service, TODO: use tracker
BundleContext ctx = FrameworkUtil.getBundle(getClass()).getBundleContext();
service = ctx.getService(ctx.getServiceReference(IWindowService.class));
// initialize callback
service.addCallback(title -> {
System.out.println("callback called " + title);
// update the property sheet ui by re-creating the items list
// updateUI();
// we can safely return null
return null;
});
// configure editor factory so the user is able to use a combobox
propertysheet.setPropertyEditorFactory(new CustomPropertyEditorFactory(service));
}
private void load() {
FXMLLoaders.loadRoot(this);
}
#Override
public Context getLocalContext() {
return context;
}
private void contextChanged() {
// find profile information
Profile found = activeContext.find(Profile.class);
// if the found profile is null, ignore it
if (found != null) {
// reset if profile is valid
if (profile != null) {
reset();
}
// create reference and register
profile = found;
register();
}
}
private void register() {
// retrieve observablelist of bean properties if some profile is selected
if(profile != null) {
ObservableList<Item> items = createDetailedList(profile);
propertysheet.getItems().setAll(items);
}
}
private void updateUI() {
// clear property elements and re-create them
reset();
// re-create items
ObservableList<Item> items = createDetailedList(profile);
propertysheet.getItems().addAll(items);
}
private ObservableList<Item> createDetailedList(Object bean) {
ObservableList<Item> list = FXCollections.observableArrayList();
try {
BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass(), Object.class);
Arrays.stream(beanInfo.getPropertyDescriptors()).map(pd -> new DetailedBeanProperty(bean, pd)).forEach(list::add);
} catch (IntrospectionException e) {
e.printStackTrace();
}
return list;
}
private void reset() {
propertysheet.getItems().clear();
}
#Override
public void setActiveContext(Context activeContext) {
this.activeContext = activeContext;
this.activeContext.addContextListener(Profile.class, event -> contextChanged());
// trigger change
contextChanged();
}
}
The current ProfilePropertiesView is just configured to display the properties of the selected profile. I want it to be able to display the current information of the last selected POJO in the UI. That means that if the user selected a Profile from the ListView, that profile should be displayed in the properties view. If he selected an Action from the Table (which is displayed in the center), the properties of the Action should be displayed.
Do I just need to register a new ContextListener for the Action.class
POJO and then call a method to populate the PropertiesView? I was
unsure if this is the right solution...
Yes, just add another ContextListener to the activeContext for every POJO type you want to observe.
Also note that in the constructor of views it's better to use a ServiceTracker instead of looking for the service via BundleContext as the service might not be available yet, depending on the order the bundles are loaded.
You can find a sample which uses a ServiceTracker here: https://stackoverflow.com/a/35974498/506855

Telerik Mail Merge - Friendly Names (using RadRichTextBox)

I think i'm missing something obvious...
I'm using the Telerik Rad controls for WPF but i assume that the Rich text box uses some similar implementation for the mail merge functionality.
I want to have some friendly names on my mail merge fields. (namely spaces in the field names)
So i have a class for instance
Public Class someclass
{
<DisplayName("This is the complex description of the field")>
Public property thisfieldnamehasacomplexdescription as string
Public property anothercomplexfield as string
}
This is the only way i know to get "Friendly" names in the dropdown that is the mail merge.
So the two fields turn up okay as :
"This is the complex description of the field"
"anothercomplexfield"
but only anothercomplexfield actually populates with data when you do the merge.
Am i going to have to template the raddropdownbutton that holds the mail merge fields?
Is there an example of this somewhere?
Also a sub question. How do i add a scroll bar on these things?
(also i know this board is not a TELERIK specific board (duh!) but this might be useful to someone in the future. So i'll copy the answer i get from Telerik into here!
http://www.telerik.com/community/forums/wpf/richtextbox/558428-radrichtextbox-mailmerge---using-displayname-to-create-a-friendly-name-with-spaces.aspx )
This is what telerik gave me:
With the default MergeFields, it is not possible to change the display name fragment of the field in order to achieve a more friendly look. This should be possible if you implement a custom MergeField by deriving from the MergeField class. Here is a sample implementation that shows how this can be done:
public class CustomMergeField : MergeField
{
private const string CustomFieldName = "CustomField";
static CustomMergeField()
{
CodeBasedFieldFactory.RegisterFieldType(CustomMergeField.CustomFieldName, () => { return new CustomMergeField(); });
}
public override string FieldTypeName
{
get
{
return CustomMergeField.CustomFieldName;
}
}
public override Field CreateInstance()
{
return new CustomMergeField();
}
protected override DocumentFragment GetDisplayNameFragment()
{
return base.CreateFragmentFromText(string.Format(Field.DisplayNameFragmentFormat, this.GetFriendlyFieldName(this.PropertyPath)));
}
private string GetFriendlyFieldName(string fieldName)
{
int lettersInEnglishAlphabet = 26;
List<char> separators = new List<char>(lettersInEnglishAlphabet);
for (int i = 0; i < lettersInEnglishAlphabet; i++)
{
separators.Add((char)('A' + i));
}
StringBuilder newFieldName = new StringBuilder();
int previousIndex = 0;
for (int i = 1; i < fieldName.Length; i++)
{
if (separators.Contains(fieldName[i]))
{
if (previousIndex > 0)
{
newFieldName.Append(" ");
}
newFieldName.Append(fieldName.Substring(previousIndex, i - previousIndex));
previousIndex = i;
}
}
newFieldName.Append(" " + fieldName.Substring(previousIndex));
return newFieldName.ToString();
}
}
Note that the fragment that is shown when the DisplayMode is Code cannot be changed.
As for your other question, you can change the content of the dropdown button to show the friendly name of the fields and to include a scrollbar in the following way:
1. First, remove the binding of the button to the InsertMergeFieldEmptyCommand from XAML and give it a name (e.g. insertMergeField).
2. Next, add the following code in code-behind:
AddMergeFieldsInDropDownContent(this.insertMergeFieldButton);
private void AddMergeFieldsInDropDownContent(RadRibbonDropDownButton radRibbonDropDownButton)
{
Grid grid = new Grid();
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(100, GridUnitType.Pixel) });
ScrollViewer scrollViewer = new ScrollViewer();
scrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Auto;
StackPanel stackPanel = new StackPanel();
foreach (string fieldName in this.editor.Document.MailMergeDataSource.GetColumnNames())
{
RadRibbonButton fieldButton = new RadRibbonButton()
{
Text = this.GetFriendlyFieldName(fieldName),
Size = ButtonSize.Medium,
HorizontalAlignment = HorizontalAlignment.Stretch,
HorizontalContentAlignment = HorizontalAlignment.Left
};
fieldButton.Command = this.editor.Commands.InsertFieldCommand;
fieldButton.CommandParameter = new MergeField() { PropertyPath = fieldName };
//or
//fieldButton.CommandParameter = new CustomMergeField() { PropertyPath = fieldName };
stackPanel.Children.Add(fieldButton);
}
stackPanel.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
scrollViewer.Content = stackPanel;
grid.Children.Add(scrollViewer);
radRibbonDropDownButton.DropDownContent = grid;
}
You can, of course optimize the code of the GetFriendlyName method and add it in a way that will be available by both classes.

Silverlight 4 Overriding the DataForm Autogenerate to insert Combo Boxes bound to Converters

I've been working towards a solution for some time and could use a little help. I know I've seen an example of this before, but tonight I cannot find anything close to what I need.
I have a service that provides me all my DropDownLists, either from Cache or from the DomainService. They are presented as IEnumerable, and are requested from the a repository with GetLookup(LookupId).
I have created a custom attribute that I have decorated my MetaDataClass that looks something like this:
[Lookup(Lookup.Products)]
public Guid ProductId
I have created a custom Data Form that is set to AutoGenerateFields and I am intercepting the autogenerate fields.
I am checking for my CustomAttribute and that works.
Given this code in my CustomDataForm (standard comments removed for brevity), what is the next step to override the field generation and place a bound combobox in its place?
public class CustomDataForm : DataForm
{
private Dictionary<string, DataField> fields = new Dictionary<string, DataField>();
public Dictionary<string, DataField> Fields
{
get { return this.fields; }
}
protected override void OnAutoGeneratingField(DataFormAutoGeneratingFieldEventArgs e)
{
PropertyInfo propertyInfo = this.CurrentItem.GetType().GetProperty(e.PropertyName);
foreach (Attribute attribute in propertyInfo.GetCustomAttributes(true))
{
LookupFieldAttribute lookupFieldAttribute = attribute as LookupFieldAttribute;
if (lookupFieldAttribute != null)
{
// Create a combo box.
// Bind it to my Lookup IEnumerable
// Set the selected item to my Field's Value
// Set the binding two way
}
}
this.fields[e.PropertyName] = e.Field;
base.OnAutoGeneratingField(e);
}
}
Any cited working examples for SL4/VS2010 would be appreciated.
Thanks
Update - Here's where I am at. I get my combo now, but it's always empty even though itemsSource is not.
if (lookupFieldAttribute != null)
{
ComboBox comboBox = new ComboBox();
Binding newBinding = e.Field.Content.GetBindingExpression(TextBox.TextProperty).ParentBinding.CreateCopy();
newBinding.Mode = BindingMode.TwoWay;
newBinding.Converter = new LookupConverter(lookupRepository);
newBinding.ConverterParameter = lookupFieldAttribute.Lookup.ToString();
comboBox.SetBinding(ComboBox.SelectedItemProperty,newBinding);
comboBox.ItemsSource = lookupRepository.GetLookup(lookupFieldAttribute.Lookup);
e.Field.Content = comboBox;
}
I found a solution.
if (lookupFieldAttribute != null)
{
ComboBox comboBox = new ComboBox();
Binding newBinding = e.Field.Content.GetBindingExpression(TextBox.TextProperty).ParentBinding.CreateCopy();
var itemsSource = lookupRepository.GetLookup(lookupFieldAttribute.Lookup);
var itemsSourceBinding = new Binding { Source = itemsSource };
comboBox.SetBinding(ItemsControl.ItemsSourceProperty, itemsSourceBinding);
newBinding.Mode = BindingMode.TwoWay;
newBinding.Converter = new LookupConverter(lookupRepository);
newBinding.ConverterParameter = lookupFieldAttribute.Lookup.ToString();
comboBox.SetBinding(ComboBox.SelectedItemProperty,newBinding);
e.Field.Content = comboBox;
}