Get first visible item in a GridView/ListView - windows-runtime

I'm showing a set of pictures in a page. I use a GridView to show the pictures. However, when the user resizes the screen to make it narrow, I switch to a ListView.
The problem now is synchronizing the scroll position for the two lists. My approach to the solution is,
1. Get the first visible item of the first list.
2. Scroll the second list to that item using ScrollIntoView
However I'm unable to see any property in GridView/ListView that gives me the first information. Any ideas?
Also any other ways of doing this are appreciated.

That seems to be just about the way I would first try to do it. You can use the ItemsPanelRoot property of the GridView/ListView and get the Children of the panel, then use TransformToVisual().TransformPoint() relative to the list control on each child to find the first one that is visible.
The one gotcha I can think of is when ScrollIntoView() would scroll the item that was first in view port in one list to show as last in view in the other one. Maybe you could get the ScrollViewer from the template of the list control (e.g. by using VisualTreeHelper) and scroll to the beginning of the list first?
The most simple way to do it all might be to just scroll to the same relative offset in the list coming into view as the one going out. It might not be very precise, but it could work.
You could even do a nice animated transition of elements in one list into the elements in the other one.
*Update
I asked around and it seems like I forgot that the default panels in GridView and ListView - the ItemsWrapGrid and ItemsStackPanel contain a FirstVisibleIndex property that could be used to get the object and then call ScrollIntoView() on the list control, which in turns takes a ScrollIntoViewAlignment enum you can use to say you want the scrolled-to-item to be the first visible (aligned to the leading edge).
*Update 2
For ListViewBase - you can also use the ListViewPersistenceHelper to get and set relative offsets.
This upcoming update to WinRT XAML Toolkit might be helpful as it would allow you to simply call: gridView.SynchronizeScrollOffset(listView); or vice versa.
public static class ItemsControlExtensions
{
public static ScrollViewer GetScrollViewer(this ItemsControl itemsControl)
{
return itemsControl.GetFirstDescendantOfType<ScrollViewer>();
}
public static int GetFirstVisibleIndex(this ItemsControl itemsControl)
{
// First checking if no items source or an empty one is used
if (itemsControl.ItemsSource == null)
{
return -1;
}
var enumItemsSource = itemsControl.ItemsSource as IEnumerable;
if (enumItemsSource != null && !enumItemsSource.GetEnumerator().MoveNext())
{
return -1;
}
// Check if a modern panel is used as an items panel
var sourcePanel = itemsControl.ItemsPanelRoot;
if (sourcePanel == null)
{
throw new InvalidOperationException("Can't get first visible index from an ItemsControl with no ItemsPanel.");
}
var isp = sourcePanel as ItemsStackPanel;
if (isp != null)
{
return isp.FirstVisibleIndex;
}
var iwg = sourcePanel as ItemsWrapGrid;
if (iwg != null)
{
return iwg.FirstVisibleIndex;
}
// Check containers for first one in view
if (sourcePanel.Children.Count == 0)
{
return -1;
}
if (itemsControl.ActualWidth == 0)
{
throw new InvalidOperationException("Can't get first visible index from an ItemsControl that is not loaded or has zero size.");
}
for (int i = 0; i < sourcePanel.Children.Count; i++)
{
var container = (FrameworkElement)sourcePanel.Children[i];
var bounds = container.TransformToVisual(itemsControl).TransformBounds(new Rect(0, 0, container.ActualWidth, container.ActualHeight));
if (bounds.Left < itemsControl.ActualWidth &&
bounds.Top < itemsControl.ActualHeight &&
bounds.Right > 0 &&
bounds.Bottom > 0)
{
return itemsControl.IndexFromContainer(container);
}
}
throw new InvalidOperationException();
}
public static void SynchronizeScrollOffset(this ItemsControl targetItemsControl, ItemsControl sourceItemsControl, bool throwOnFail = false)
{
var firstVisibleIndex = sourceItemsControl.GetFirstVisibleIndex();
if (firstVisibleIndex == -1)
{
if (throwOnFail)
{
throw new InvalidOperationException();
}
return;
}
var targetListBox = targetItemsControl as ListBox;
if (targetListBox != null)
{
targetListBox.ScrollIntoView(sourceItemsControl.IndexFromContainer(sourceItemsControl.ContainerFromIndex(firstVisibleIndex)));
return;
}
var targetListViewBase = targetItemsControl as ListViewBase;
if (targetListViewBase != null)
{
targetListViewBase.ScrollIntoView(sourceItemsControl.IndexFromContainer(sourceItemsControl.ContainerFromIndex(firstVisibleIndex)), ScrollIntoViewAlignment.Leading);
return;
}
var scrollViewer = targetItemsControl.GetScrollViewer();
if (scrollViewer != null)
{
var container = (FrameworkElement) targetItemsControl.ContainerFromIndex(firstVisibleIndex);
var position = container.TransformToVisual(scrollViewer).TransformPoint(new Point());
scrollViewer.ChangeView(scrollViewer.HorizontalOffset + position.X, scrollViewer.VerticalOffset + position.Y, null);
}
}
}

Related

Reorder the context menu and add dividers in Forge Viewer

Is there a way to rearrange the order of the context menu items and possibly add dividers between them for grouping?
I followed this post https://forge.autodesk.com/blog/customize-viewer-context-menu to make a custom context menu.
For dividers I found functionality in the source code:
menu.push({divider: true})
Snippet of the source code for show() in contextMenu:
for (var i = 0; i < menu.length; ++i) {
var defn = menu[i],
title = defn.title,
target = defn.target,
icon = defn.icon,
shortcut = defn.shortcut,
divider = defn.divider;
var hasChildren = Array.isArray(target);
if (hasChildren && !menu.isChild) {
addExpandDiv = true; // Keep track of any item that is expandable
} else {
// As described in the design, limit the the number of menu levels to two.
// We will flatten the target array
hasChildren = false;
}
if (divider) {
menuItem = this.createDivider();
menuDiv.appendChild(menuItem);
} else {

Apps-Script: Element equality?

Since this is always false:
doc.getBody().getParagraphs()[0] == doc.getBody().getParagraphs()[0]
How do you test element equality in Apps-Script?
I'm not entirely sure if you are comparing the contents or the position. Let's assume you can compare the contents with getAsText().
To compare the position, it's fairly easy to create an element index (the path at which an element appears in a document).
/**
* create a path in document serial for an element
* #param {Document.Element} element an element
* #param {string} [path=''] the path so far
* #return {string} the path
*/
function pathInDocument (element,path) {
path = path || "" ;
var parent = element.getParent();
if (parent) {
path = pathInDocument( parent , Utilities.formatString ( '%04d.%s', parent.getChildIndex(element),path ));
}
return path;
};
which can be called like this
var path = pathInDocument(element);
and will return something like
0000.0001.0004....etc
If the paths of two elements are the same, they appear in the same position in the document and are therefore the same element.
For an example of using this (in this case to sort bookmarks) see https://ramblings.mcpher.com/google-docs/sorting-bookmarks-in-a-document/
Eventually I came up with a solution for comparing elements.
first of all let me point that this code works and returns true:
var paragraphs = doc.getBody().getParagraphs();
Logger.log(paragraphs[0] == paragraphs[0]);
that is because you are comparing the same element from an array. The way you did in the question, you had two different arrays of paragraphs.
However there are situations when you can not do that, because you may not be comparing paragraphs, or you don't even know what elements you have.
What I do is create a path to the elements all the way up to the body section of the Document. If the paths are equal, you have the same elements.
function bodyPath(el, path) {
path = path? path: [];
var parent = el.getParent();
var index = parent.getChildIndex(el);
path.push(index);
var parentType = parent.getType();
if (parentType !== DocumentApp.ElementType.BODY_SECTION) {
path = bodyPath(parent, path);
} else {
return path;
};
return path;
};
function isSameElement(element1, element2) {
var path1 = bodyPath(element1);
var path2 = bodyPath(element2);
if (path1.length == path2.length) {
for (var i=0; i<path1.length; i++) {
if (path1[i] !== path2[i]) {
return false;
};
};
} else {
return false;
};
return true;
};
This method has proved itself quite fast. Any additions are welcome!
I wrote a recursive solution to avoid string comparison and short-circuit the path walk. Note that you can always convert to loops if you're not happy with the stack dependency of recursion.
function isSameElement(elem1, elem2) {
if (!elem1 && !elem2) return true;
if (!elem1 || !elem2) return false;
var p1=elem1.getParent();
var p2=elem2.getParent();
if (!p1 && !p2) {
return true;
} else if (!p1 || !p2) {
return false;
} else if (p1.getChildIndex(elem1)==p2.getChildIndex(elem2)){
return isSameElement(p1,p2);
} else {
return false;
}
}
I tried it and its always false, for some reason the method returns different objects.
In this case you are comparing the objects and not the content of the objects which indeed are different. You could get the content of the object with .getText(), then this comparison would return true.

Out of memory - 15+ PivotItems in PivotControl

I have a pivot control that its ItemsSource is databound to a collection of items.
The collection can be up to 50 items, so it means that it can be up to 50 PivotItems.
Each PivotItem contains Image control and some text controls.
After swiping between the pivot pages (about 15), when pressing the back button, the app crashs with "Out of Memory" exception.
Ofcourse the problem is in the PivotItems that takes all the memory each time I swipe to a new one.
I know the guildlines not recommend to use pivot with more than 6-7 items, but I really need it that way.
I saw some answer on the web that advices to use ContentControl with Content binded to my item layout as Resource.. but I find it hard to Implement.
Any more suggestions how to achieve the desirable result (no memory overflow)?
Thanks!
Edit:
I tried to manipulate the items by taking only 3, the current, the next and the previous.
But the behavior of the pivot is not good.. How should I increment/decrement the Pivot.SelectedIndex?
private void GestureListener_Flick(object sender, FlickGestureEventArgs e)
{
if (e.Direction.ToString() == "Horizontal") //Left or right
{
var itemVM = this.DataContext as ItemViewModel;
int i = pivot.SelectedIndex;
ObservableCollection<Item> items = new ObservableCollection<Item>();
var currentItemIndex = itemVM.CurrentReader.Reader.Items.IndexOf(itemVM.SelectedItem);
if (e.HorizontalVelocity > 0) //Right
{
currentItemIndex++;
}
else //Left
{
currentItemIndex--;
}
if (itemVM.AllItems.Count >= 3)
{
if (currentItemIndex == 0) // in case we are in the first item m in the AllItems collection.
{
items.Add(itemVM.AllItems[itemVM.AllItems.Count - 1]);
items.Add(itemVM.AllItems[currentItemIndex]);
items.Add(itemVM.AllItems[currentItemIndex + 1]);
}
else if (currentItemIndex == itemVM.AllItems.Count - 1) // in case we are in the last item in the AllItems collection.
{
items.Add(itemVM.AllItems[currentItemIndex - 1]);
items.Add(itemVM.AllItems[currentItemIndex]);
items.Add(itemVM.AllItems[0]);
}
else
{
items.Add(itemVM.AllItems[currentItemIndex - 1]);
items.Add(itemVM.AllItems[currentItemIndex]);
items.Add(itemVM.AllItems[currentItemIndex + 1]);
}
}
else // in case we have only 3 items, no manpulation needed.
{
items = itemVM.AllItems;
}
itemVM.CurrentItemsCollection = items; // only 3 items
}
}

How do you create an if statement to check if many objects style == dispaly:none?

I'm making a memory-match game in which you flip two cards over in order to get them to match.
I'm doing it with simple if statements as shown below:
if(click == 2) //denotes two cards being clicked
{
if(flippedArray[1].src === flippedArray[0].src) // if click 1 == click 2 then refer to function 'delayMatch' which sets click 1 and 2 cards to not be displayed
{
window.setTimeout(function() { delayMatch() }, 500);
console.log("EQUAL");
score = +25000;
}
else
{
window.setTimeout(function() { delayNoMatch() }, 500); // if click 1 != click 2 then display card.png
console.log("NOT EQUAL");
score = -1999;
}
function delayMatch() //function for matching pairs
{
flippedArray[0].style = "display:none;";
flippedArray[1].style = "display:none;";
}
function delayNoMatch() //function for non-matching pairs
{
flippedArray[0].src = "card.png";
flippedArray[1].src = "card.png";
}
click = 0; // when clicked two cards set click back to zero
}
As you can see if two cards match they're set to display:none. What I'm trying to do is link to an "end game" html page once all 36 divs are set to display: none or I guess once the function delayMatch() has been called 18 times.
I'm completely at a loss as how I can do this.
my goal is something like this:
flippedArray[0] and flippedArray[1] is just a temporary array to check if the two cards currently in play are a match or not.
I was thinking something like:
endGameCounter =0;
endGameCounter++; //in the matching if-statement
then if(endGameCounter == 18)
{
location.href='link here'
}
You can use one variable for count inside the page.
If you are doing it in JSP, JSTL can help you here.
use to set a variable and just check the value time to time.
c:set var="COUNT" value="SOMETHING"/>
Try
function GameIsOver(){
for (var i = 0; i < allCards.length; i++) {
if(allCards[i].style.display === 'none')return false;
}
return true;
}

how to compare two array collection using action script

how to compare two arraycollection
collectionArray1 = ({first: 'Dave', last: 'Matthews'},...........n values
collectionArray = ({first: 'Dave', last: 'Matthews'},...........n values
how to compare..if equal just alert nochange if not alert chaged
If you just want to know if they are different from each other, meaning by length, order or individual items, you can do the following, which first checks to see if the lengths are different, then checks to see if the individual elements are different. This isn't terribly reusable, it's left as an exercise for the reader to split this apart into cleaner chunks :)
public function foo(coll1:ArrayCollection, coll2:ArrayCollection):void {
if (coll1.length == coll2.length) {
for (var i:int = 0; i < coll1.length; i++) {
if (coll1[i].first != coll2[i].first || coll1[i].last != coll2[i].last) {
Alert.show("Different");
return;
}
}
}
Alert.show("Same");
}
/* elements need to implement valueOf
public function valueOf():Object{}
*/
public static function equalsByValueOf(
first:ArrayCollection,
seconde:ArrayCollection):Boolean{
if((first==null) != (seconde==null) ){
return false;
}else if(!first && !seconde){
return false;
}
if(first.length!=seconde.length){
return false;
}
var commonLength:int = first.length;
var dictionary:Dictionary = new Dictionary();
for(var i:int=0;i<commonLength;i++){
var item1:Object = first.getItemAt(i);
var item2:Object = seconde.getItemAt(i);
dictionary[item1.valueOf()]=i;
dictionary[item2.valueOf()]=i;
}
var count:int = 0;
for (var key:Object in dictionary)
{
count++;
}
return count==commonLength;
}
/* valueOf sample
* something like javaObject.hashCode()
* use non changing fields(recommended)
*/
public function valueOf():Object{
return "_"+nonChangeField1+"_"+nonChangeField2+"...";
}
I was going to say this.
if(collectionArray === collectionArray1)
But that wont work (not triple = signs). As === is used to see classes.
I would write a function called check if object exists in array.
Create an array to hold elements that are not found. eg notFound
in Collection1 go through all the element and see if they exist in Collection2, if an element does not exist, add it to the notFound array. Use the function your created in step1
Now check Collection2, if an element is not found add it to the notFound array.
There is no 5.
Dude, use the mx.utils.ObjectUtil... the creators of actionscript have already thought about this.
ObjectUtil.compare(collection1, collection2) == 0;