Restore Scroll Position in LongListSelector after tombstone - windows-phone-7

I'm trying to work with the LongListSelector control from the WP7 Silverlight Toolkit. It's taken a bit of work, but I finally have it working with my app. Unfortunately, I am having some trouble properly handling the tombstoning process.
When the application tombstones (or the user navigates to another page by selecting an item in the list), I save a copy of the topmost visible item in the list. I save it to both a class variable and to the app state collection.
ICollection<object> visibleItems = myLongList.GetItemsInView();
_lastItem = null;
if (visibleItems.Count > 0)
_lastItem = visibleItems.First();
IDictionary<string, object> state =
Microsoft.Phone.Shell.PhoneApplicationService.Current.State;
state["IndexByName_LastTopItem"] = _lastItem;
Then, when the user returns to the page I check for one of the two values (state or variable) and use it to restore the last scroll position.
if (_lastItem == null)
{
if (state.ContainsKey("IndexByName_LastTopItem"))
{
_lastItem = state["IndexByName_LastTopItem"] as Chemical;
}
}
if (_lastItem != null)
Dispatcher.BeginInvoke(() => { myLongList.ScrollTo(_lastItem); });
This works great unless the application tombstones. In that case I don't get any errors, but the list is completely blank until I touch it and drag. Once I do that, it redisplays at the top of the list. I took a look at the source for the control and found that when you call .ScrollTo(object) it doesn't get a match. Further investigation identified that when searching for an item to scroll to, it compares using == instead of Equals. I only overrode Equals, and apparently the default == compares (by design) references. When you restore a State item after tombstoning the references don't match.
I can override ==, but that feels wrong. I can change and rebuild the control source to call equals instead (I tried and it worked), but it was written by people much smarter than I and I'm wondering if I just don't get it.
Is there a better way?

This is the fix I ended up coming up with...
Since the source code is freely available for the Toolkit, I ended up editing the LongListSelector source code to call .Equals instead of ==. It seems to work properly for my use case and I thought I'd share in case anyone else finds it useful...
in LongListSelector.cs find the GetFlattenedIndex(object item) function and replace
if (item == _flattenedItems[index].Item)
with
if (item.Equals(_flattenedItems[index].Item))
and then in the same file find the GetResolvedIndex(object item, out ContentPresenter contentPresenter) function and replace
if (node.Value.Content == item) // Nov 2010 Release
// OR
if (_flattenedItems[index].Item == item) // Feb 2011 Release
with
if (item.Equals(node.Value.Content)) // Nov 2010 Release
// OR
if (item.Equals(_flattenedItems[index].Item)) // Feb 2011 Release
NOTE that the replace depends on which toolkit download you are using!
Once you have made these changes to the control, it will properly match up objects specified in ScrollTo(object), even if the references are not equal as long as you properly override Equals for all object types displayed in your LongListSelector. Don't forget this applies to your Grouping class as well as your item class if you have a grouped list!

Can you try to get the item in the new list ?
var _goodReference = myList.FirstOrDefault(x => x.id == _lastItem.Id);
if (_goodReference != null)
Dispatcher.BeginInvoke(() => { myLongList.ScrollTo(_goodReference); });

Related

PanelTabbed control preserves tab selection even after the taskflow ends

I am facing an issue where I have a A.jsff(taskflow A) and a region inside A.jsff called aB.jsff (taskflow aB).
Inside ab.jsff I have the panelTabbed control with two tabs (showDetailItem) tab1 and tab2.
I also have a save button in the A.jsff which commits the changes on the page.
I want the tab1 to be open whenever this taskflow is run.
The problem is, no matter which tab I select and click save(taskflow A), when I come back to this page, the tab which was previously expanded is always disclosed. Even after setting disclosed attribute and persist/dontPersist attribute, I wasn't able to achieve my requirement.
I also found few related discussions on the web which didn't help at all.
Can anyone please help or provide workaround to achieve this.
This is a tech stack error. And I have been provided with a fix which can help others who can face same issue.
Create a binding for the panelTabbed element and add this to its setter function -
public void setPanelTabBinding(RichPanelTabbed panelTabBinding) {
/*
* Tech stack bug fix for default tab selection.
*/
this.panelTabBinding = panelTabBinding;
ComponentChangeFilter[] compChgFilters = null;
if (panelTabBinding != null && compChgFilters.length == 0) {
compChgFilters = panelTabBinding.getComponentChangeFilters();
if (compChgFilters != null)
panelTabBinding.addComponentChangeFilter(new TreeRestrictingChangeFilter(panelTabBinding));
}
}

NSOutlineView reloadItem/reloadData not working when replacing an item

I have a view-based NSOutlineView with a dataSource/delegate model instead of binding to a tree controller (I want control over the insert/update animations).
I'm replacing an item in my model and would like to update that specific row in the outline view without having to call reloadData().
I cannot get this to work. Either the item does not update at all or the item's expanded state doesn't update. There seems to be some caching being done inside of NSOutlineView according to this, but even with these suggestions, I could not get it to work.
What I have:
(1) The outline view represents a folder structure
(2) At first, there is a singe file:
(3) The file is then replaced with a folder item:
// Model update
let oldFileItem = rootItem.children.first!
rootItem.children.remove(at: 0)
rootItem.children.append(Item(title: "Folder", children:[], isExpandable:true))
Expected result:
Actual result (reloadItem):
outlineView.reloadItem(oldFileItem) // I kept a reference
Icon and title reloaded, but note that the expansion triangle is missing.
I can somewhat understand that reloadItem() might not work in this case, because the old item is not part of the data model anymore. Strangely enough, the item's title and icon update, but not the expansion state.
Actual result (reloadData(forRowIndexes:columnIndexes:):
outlineView.reloadData(forRowIndexes: IndexSet(integer:0), columnIndexes: IndexSet(integer:0))
No effect whatsoever. This is the one that I would have expected to work.
Actual result (remove/insert):
outlineView.removeItems(at: IndexSet(integer:0), inParent: rootItem, withAnimation: [])
outlineView.insertItems(at: IndexSet(integer:0), inParent: rootItem, withAnimation: [])
No effect whatsoever.
The docs say about removeItems(): "The method does nothing if parent is not expanded." and isExpanded does indeed return false for the root node, although its children are visible. Is this special behavior for items that are direct children of the root node? What am I missing here?
For reference, my data model:
class Item:NSObject {
var title:String
var children:[Item]
var isExpandable:Bool
init(title:String, children:[Item], isExpandable:Bool) {
self.title = title
self.children = children
self.isExpandable = isExpandable
}
}
For reference:
It turned out to be an issue with how I used the API. NSOutlineView.removeItems/insertItems expect nil for the inParent parameter for the root item. I was handing in the actual root item. Using nil instead of the root item solved the problem.

Check programmatically if a value is compliant with a datavalidation rule

I am using data validation rules on a Google Spreadsheet.
In my scenario, I need users to entry only valid values. I use the 'Reject input' to force them to write only validated content.
However, the 'Reject input' option works for manually entried data only, but it does not work if the user pastes content into the cell from a different source (e.g. a MS Excel document). In that case, a warning is still shown but the invalid value is written.
In other words, I need the 'Reject input' option to work also with pasted content.
OR... another approach would be to programmatically check the validity of the value according the Datavalidation rule for that cell.
Any ideas?
Thank you in advance.
I had a little play with this.
I had inconsistent behavior from google.
On occasion when I ctrl-c and ctrl-p, the target cell lost its data validation!
To do this programmatically
Write myfunction(e)
Set it to run when the spreadsheet is edited, do this by Resources>Current Project's Triggers
Query e to see what has happened.
Use the following to gather parameters
var sheet = e.source.getActiveSheet();
var sheetname = sheet.getSheetName();
var a_range = sheet.getActiveRange();
var activecell = e.source.getActiveCell();
var col = activecell.getColumn();
var row = activecell.getRow();
You may wish to check a_range to make sure they have not copied and pasted multiple cells.
Find out if the edit happened in an area that you have data validated;
if (sheetname == "mySheet") {
// checking they have not just cleared the cell
if (col == # && activecell.getValue() != "") {
THIS IS WHERE YOU CHECK THE activecell.getValue() AGAINST YOUR DATA VALIDATION
AND USE
activecell.setValue("") ;
to clear the cell if you want to reject the value
}
}
The obvious problem with this is that it is essentially repeating programmatically what the data validation should already be doing.
So you have to keep two sets of validation rules synchronized. You could just delete the in sheet data validation but I find that useful for providing the user feedback. Also is the data validation you are using provides content it is practical to leave it in place.
It would be great if there was a way of detecting that ctrl-p had been used or one of the paste-special options and only run the script in those cases. I would really like to know the answer to that. Can't find anything to help you there.
Note also that if someone inserts a row, this will not trigger any data validation and the onEdit() trigger will not detect it. It only works when the sheet is edited and by this I think it means there is a content change.
onChange() should detect insertion, it is described as;
Specifies a trigger that will fire when the spreadsheet's content or
structure is changed.
I am posting another answer because this is a programmatic solution.
It has a lot of problems and is pretty slow but I am demonstrating the process not the efficiency of the code.
It is slow. It will be possible to make this run faster.
It assumes that a single cell is pasted.
It does not cater for inserting of rows or columns.
This is what I noticed
The onEdit(event) has certain properties that are accessible. I could not be sure I got a full listing and one would be appreciated. Look at the Spreadsheet Edit Events section here.
The property of interest is "e.value".
I noticed that if you typed into a cell e.value = "value types" but if you pasted or Paste->special then e.value = undefined. This is also true for if you delete a cell content, I am not sure of other cases.
This is a solution
Here is a spreadsheet and script that detects if the user has typed, pasted or deleted into a specific cell. It also detects a user select from Data validation.
Type, paste or delete into the gold cell C3 or select the dropdown green cell C4.
You will need to request access, if you can't wait just copy & paste the code, set a trigger and play with it.
Example
Code
Set the trigger onEdit() to call this or rename it to onEdit(event)
You can attach it to a blank sheet and it will write to cells(5,3) and (6,3).
function detectPaste(event) {
var sheet = event.source.getActiveSheet();
var input_type =" ";
if (event.value == undefined) { // paste has occured
var activecell = event.source.getActiveCell();
if (activecell.getValue() == "") { // either delete or paste of empty cell
sheet.getRange(5,3).setValue("What a good User you are!");
sheet.getRange(6,3).setValue(event.value);
input_type = "delete"
}
else {
activecell.setValue("");
sheet.getRange(5,3).setValue("You pasted so I removed it");
sheet.getRange(6,3).setValue(event.value);
input_type = "paste";
}
}
else { // valid input
sheet.getRange(5,3).setValue("What a good User you are!");
sheet.getRange(6,3).setValue(event.value);
input_type = "type or select";
}
SpreadsheetApp.getActiveSpreadsheet().toast('Input type = ' + input_type, 'User Input detected ', 3);
}

Mvvm - Updating Binding takes too long

I have a 5 ViewModels. Every view has their own VM. When I start the program, the ViewModel changes the bindings, i.e.
private string _bruttolohn;
public string Bruttolohn
{
get { return _bruttolohn; }
set
{
if (value != null)
{
if (value != _bruttolohn)
{
_bruttolohn = value;
Calculate();
RaisePropertyChanged(() => Bruttolohn);
}
}
}
}
Bruttolohn = some value I put in to calculate and set the new values. I have ~100 other propertys, which are all using Binding. If I start the program, the calculation is fine and works fast! But when I change the View (All views are in a ContentControl. If I click on a button, the view is being changed like this: MyContent.Content = new FirstView();). Now here's the problem: If I change the view ~30 times, the ViewModel needs way too long to set the bindings. Why!?
+ If I debug and I'm at this point:
RaisePropertyChanged(() => Bruttolohn);
Error pops up: ObservableObject.cs is missing...?
There is nothing wrong with the code you've shown so I suspect the Calculation method. Profile it.
If the Calculation() method is too long to post, it shouldn't be in a property setter :)
I guess I found the solution. Every time I click on the button "FirstView", the Content of the ContentControl gets a NEW VIEW()! So every time I click on the button, it'll do:
MyContent.Content = new FirstView();
This means that there are still somehow Views in the background open... I can't explain it by myself, but if I set the View globally like
FirstView fw;
and say in the constructor of the MainViewModel
fw = new FirstView();
I can easily assign the content of my ContentControl like this (without creating a new View):
MyContent.Content = fw;
You can download a test app here: http://www65.zippyshare.com/v/89159114/file.html
It has a MainView and 2 Views. It shows my problem. If you click on the Button "FirstView", the content of the contentcontrol will be "FirstView.xaml". It has 2 textboxes, which calculate the sum and has a for-loop (for i = 0, i < 500, i++). If you calculate right at the start, the calculation takes only ~1 sec, but if you click the Button "FirstView" ~30 times and try to calculate again, the calculation takes more than 10 sec!!!
I thinks it's because of MyContent.Content = new FirstView(); You can check my program and correct me if I'm wrong or say if I'm right... Like I said, I think that this is the solution, but I'm not 100% sure.
if Calculate() isn't an async method, it blocks the UI.
You could use
Task.Run(() =>
{
Calculate();
RaisePropertyChanged(() => Bruttolohn);
});
Not relevant to your question, but maybe interesting:
if (value != _bruttolohn)
Strings should be compared using .Equals, as the same string can be referenced to different places in the heap.
So:
if (value == null || !value.Equals(_bruttolohn))

Programmatically select and clear selection on an ADF DVT piegraph

I am using a pieGraph and doing some page interactions based on clicking on the pie-graph. These work just fine.
<dvt:pieGraph id="graph1" tabularData="#{dc.bean.tabularData}" dataSelection="single"
selectionListener="#{dc.bean.transfersGraphSelectionListener}"/>
However I am not able to support the following use cases
Clicking outside the graph(or clicking a selected data set again) should cause the pie-graph to lose its selection.
Having a clear button on the page which forces the graph to lose its current selection.
Programmatically select one of the data sets in the graph
I checked the UIGraph API but couldn't find much information.
Any hints would be really helpful.
please add the right code to your original post. this is what your code look like
transfersGraphSelectionListener(SelectionEvent selectionEvent){
Set<GraphSelection> selectionSet = selectionEvent.getGraphSelection();
for (GraphSelection selection : selectionSet) {
if (selection instanceof DataSelection) {
DataSelection ds = (DataSelection) selection;
Set seriesKeySet = ds.getSeriesKey().keySet();
for (Object key : seriesKeySet) {
Object selectedKey = ds.getSeriesKey().get((String) key))
}
Looks like something is missing!

Resources