Grid scrolling after render ZK - grid

I'm trying to scroll down to the bottom of a grid, after the model was setted.
1) I set the model:
myGrid.setModel(new ListModelList<Object>(myList));
2) I override the row renderer
myGrid.setRowRenderer(new RowRenderer<Object>() {
#Override
public synchronized void render(Row row,final Object data, int index) throws Exception {
row.setStyle("commonCellPadding");
.
.
.
row.appendChild(htmlMessage);
}
});
3) Finally if the list used to set the model is too big (the grid in the .zul has fixed height) i want to show the last results (the more recents in this case).
I need to scroll down automatically after the render. How can i do this?
Things i had try
a) Calling a javascript function after the render, this doesn't work due to the fact that the gridEle.scrollHeight attribute returns the fixed height of the grid setted in the zul (or 0 if not) and not the grid's height after the model was setted.
myGrid.addEventListener(ZulEvents.ON_AFTER_RENDER, new EventListener<Event>() {
public void onEvent(Event event) throws Exception
{
Clients.evalJavaScript("var gridEle = document.getElementById('"+myGrid.getUuid()+"-body"+"'); gridEle.scrollTop = gridEle.scrollHeight;alert(gridEle.scrollHeight);");
}
});

Why don't you sort the myList descending at first to make the last row become the first one instead to control the scroll bar?
In my opinion, it would be more easy and matching the users experience.

Just call Clients.scrollIntoView(rows.getLastChild()); after you have set the model and row renderer (provided rows is Rows component id and is already auto wired into your controller). See the live demo on zkfiddle here and source
UPDATE: Clients.scrollIntoView(Component) wouldn't work if you are using Render-On-Demand feature because naturally if the row that you want to scroll to wouldn't have been loaded on initial page load.

Related

How to detect SizeChanged event after using .HeightRequest

I noticed that after using the .HeightRequest property on a View in Xamarin.Forms that a method I have assigned to the SizeChanged event is no longer called.
The content within the View that had called .HeightRequest is a Label that contains text which can be zoomed in. When zooming in, the text now exceeds the "bounds" of the View's height, and the height is no longer adjusting as it once was.
public ExpandableEntryView(...)
{
InitializeComponent();
// my beautiful code
SizeChanged += ExpandableEntryView_SizeChanged;
UpdateUI();
}
private void ExpandableEntryView_SizeChanged(object sender, EventArgs e)
{
// this is never called once .HeightRequest is assigned a value
}
private void SomeFunction() {
sectionsStackLayout.HeightRequest = 0; // this causes SizeChanged to no longer work
}
I essentially need this View to minimize its height to 0, and then return to its original height when I tell it to. I am also using IsVisible, but the Height property is required for an animation I am trying to do.
Thank you in advance.
Nevermind, I was able to figure it out. The reason why the SizeChanged event is no longer called is because the view is no longer responsible for keeping track of its size after assigning a value to HeightRequest. So, if you would like for your View to return to being able to determine its own size once again, (i.e. if a child element in that View happens to change size/scale) you need to assign the HeightRequest to -1. This will then reenable the SizeChanged event.
Hope this helps somebody!

When is view frame size set in iOS10 SDK?

For several years in Swift and ObjC I've used this technique to make a circular view:
view.layer.cornerRadius = view.frame.size.width / 2
view.clipsToBounds = true
When UILayoutConstraints in the Storyboard are fixed width / height there has been no problem putting this code in viewDidLoad, or in viewWillAppear. Built in iOS9.3SDK it runs fine in iOS10 etc.
iOS10SDK shows framesize completely different to the fixed size in the Storyboard, even up to viewWillAppear, and in viewDidLayoutSubviews etc. My options are:
1) do this in viewDidAppear (awful solution)
2) hardcode the cornerRadius (works fine, but awful)
This looks like a bug (as a fixed width/height set in Storyboard should never be changed without at least a warning in the console). Is it, or is there a different place for this code now? (screenshot of test code attached)
In iOS 10, I met this issue right after I started to use Xcode 8 in june, when it was beta. This solution is needed:
Put layoutIfNeeded() right before your layer modifications:
self.view.layoutIfNeeded()
view.layer.cornerRadius = view.frame.size.width / 2
view.clipsToBounds = true
OR
place the code for corner radius to viewDidLayoutSubviews() method:
override func viewDidLayoutSubviews() {
view.layer.cornerRadius = view.frame.size.width / 2
view.clipsToBounds = true
}
This was bugging me all day today. I had the same issue in a few points I needed the correct frame size to draw custom UI elements, or apply style to views, particularly changing the corner radius. The solution I found was creating a subclass of UIViewController with the following implementation:
class BaseViewController: UIViewController {
/**
This is a helper property and a helper method that will be called
when the frame is calculated, but will prevent the formatting code
being called more than once
*/
private var didCalculateInitialFrame = false
private func checkIfFrameWasCalculated() {
if self.didCalculateInitialFrame == false {
self.viewDidCalculateInitialFrame()
self.didCalculateInitialFrame = true
}
}
//-- This is where you will tie your helper method with the view lifecycle
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.checkIfFrameWasCalculated()
}
/**
This is an overridable function that you will implement
in your subclasses, in case you need to use this approach
in multiple places of your app.
*/
func viewDidCalculateInitialFrame() {
}
}
This fixed all my issues with the storyboard. I hope this is helpful.
This is seems problem in ios10
it can be solved by overriding the controller method viewDidLayoutSubviews() or
call method layoutIfNeeded() for the view which dimension value you might need before getting its dimension.
Objective-C :[viewObject layoutIfNeeded]
Swift: viewObject.layoutIfNeeded()
if the dimension value is still not correct then use layoutIfNeeded method for the parent view of the view object.

How to detect QTableWidget scroll source (code itself/user (wheel)/user (scrollbar))?

I'm writing a program using Qt 4.8 that displays a table (QTableWidget) filled with filenames and file's params. First an user adds files to the list and then clicks process. The code itself updates the contents of the table with simple progress description. I want the table by default to be scrolled automatically to show the last processed file and that code is ready.
If I want to scroll it by hand the widget is being scrolled automatically as soon as something changes moving the viewport to the last element. I want to be able to override the automated scroll if I detect that it was the user who wanted to change view.
This behavior can be seen in many terminal emulator programs. When there's a new line added the view is scrolled but when user forces the terminal to see some previous lines the terminal does not try to scroll down.
How could I do that?
Solution:
I created an object which filters event processed by my QTableWidget and QScrollBar embedded inside. If I spot the event that should turn off automatic scrolling I just set a flag and stop scrolling view if that flag is set.
Everything is implemented inside tableController class. Here are parts of three crucial methods.
bool tableController::eventFilter(QObject* object, QEvent* event)
{
switch (event->type())
{
case QEvent::KeyPress:
case QEvent::KeyRelease:
case QEvent::Wheel:
case QEvent::MouseButtonDblClick:
case QEvent::MouseButtonPress:
case QEvent::MouseButtonRelease:
_autoScrollEnabled = false;
default:
break;
}
return QObject::eventFilter(object, event);
}
void tableController::changeFile(int idx)
{
[...]
if (_autoScrollEnabled)
{
QTableWidgetItem* s = _table.item(_engine.getLastProcessed(), 1);
_table.scrollToItem(s);
}
[...]
}
void tableController::tableController()
{
[...]
_autoScrollEnabled = true;
_table.installEventFilter(this);
_table.verticalTableScrollbar()->installEventFilter(this);
[...]
}
Thanks for all the help. I hope somebody will find it useful :)
Subclass QTableWidget and overload its wheelEvent. You can use the parameters of the supplied QWheelEvent object in order to determine if the user scrolled up or down.
Then use a simple boolean flag which is set (or reset) in your wheelEvent override. The method which is responsible for calling scrollToBottom() should then consider this boolean flag.
You will have to find a way to figure out when to set or reset that flag, e.g. always set it when the user scrolls up and reset it when the user scrolls down and the currently displayed area is at the bottom.
connect(_table->view()->verticalScrollBar(), &QAbstractSlider::actionTriggered, this, [this](int) {
_autoScrollEnabled = false;
});

How to keep a list from scrolling on dataProvider refresh/update/change?

I have a simple list and a background refresh protocol.
When the list is scrolled down, the refresh scrolls it back to the top. I want to stop this.
I have tried catching the COLLECTION_CHANGE event and
validateNow(); // try to get the component to reset to the new data
list.ensureIndexIsVisible(previousIndex); // actually, I search for the previous data id in the IList, but that's not important
This fails because the list resets itself after the change (in DataGroup.commitProperties).
I hate to use a Timer, ENTER_FRAME, or callLater(), but I cannot seem to figure out a way.
The only other alternatives I can see is sub-classing the List so it can catch the dataProviderChanged event the DataGroup in the skin is throwing.
Any ideas?
Actually MUCH better solution to this is to extend DataGroup. You need to override this.
All the solutions here create a flicker as the scrollbar gets resetted to 0 and the it's set back to the previous value. That looks wrong. This solution works without any flicker and the best of all, you just change DataGroup to FixedDataGroup in your code and it works, no other changes in code are needed ;).
Enjoy guys.
public class FixedDataGroup extends spark.components.DataGroup
{
private var _dataProviderChanged:Boolean;
private var _lastScrollPosition:Number = 0;
public function FixedDataGroup()
{
super();
}
override public function set dataProvider(value:IList):void
{
if ( this.dataProvider != null && value != this.dataProvider )
{
dataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE, onDataProviderChanged);
}
super.dataProvider = value;
if ( value != null )
{
value.addEventListener(CollectionEvent.COLLECTION_CHANGE, onDataProviderChanged);
}
}
override protected function commitProperties():void
{
var lastScrollPosition:Number = _lastScrollPosition;
super.commitProperties();
if ( _dataProviderChanged )
{
verticalScrollPosition = lastScrollPosition;
}
}
private function onDataProviderChanged(e:CollectionEvent):void
{
_dataProviderChanged = true;
invalidateProperties();
}
override public function set verticalScrollPosition(value:Number):void
{
super.verticalScrollPosition = value;
_lastScrollPosition = value;
}
}
I ll try to explain my approach...If you are still unsure let me know and I ll give you the source code as well.
1) Create a variable to store the current scroll position of the viewport.
2) Add Event listener for Event.CHANGE and MouseEvent.MOUSE_WHEEL on the scroller and update the variable created in step 1 with the current scroll position;
3) Add a event listener on your viewport for FlexEvent.UpdateComplete and set the scroll position to the variable stored.
In a nutshell, what we are doing is to have the scroll position stored in variable every time user interacts with it and when our viewport is updated (due to dataprovider change) we just set the scroll position we have stored previously in the variable.
I have faced this problem before and solved it by using a data proxy pattern with a matcher. Write a matcher for your collection that supports your list by updating only changed objects and by updating only attributes for existing objects. The goal is to avoid creation of new objects when your data source refreshes.
When you have new data for the list (after a refresh), loop through your list of new data objects, copying attributes from these objects into the objects in the collection supporting your list. Typically you will match the objects based on id. Any objects in the new list that did not exist in the old one get added. Your scroll position will normally not change and any selections are usually kept.
Here is an example.
for each(newObject:Object in newArrayValues){
var found:Boolean = false;
for each(oldObject:Object in oldArrayValues){
if(oldObject.id == newObject.id){
found = true;
oldObject.myAttribute = newObject.myAttribute;
oldObject.myAttribute2 = newObject.myAttribute2;
}
}
if(!found){
oldArrayValues.addItem(newObject);
}
}
My solution for this problem was targeting a specific situation, but it has the advantage of being very simple so perhaps you can draw something that fits your needs from it. Since I don't know exactly what issue you're trying to solve I'll give you a description of mine:
I had a List that was progressively loading data from the server. When the user scrolled down and the next batch of items would be added to the dataprovider, the scrollposition would jump back to the start.
The solution for this was as simple as stopping the propagation of the COLLECTION_CHANGE event so that the List wouldn't catch it.
myDataProvider.addEventListener(
CollectionEvent.COLLECTION_CHANGE, preventRefresh
);
private function preventRefresh(event:CollectionEvent):void {
event.stopImmediatePropagation();
}
You have to know that this effectively prevents a redraw of the List component, hence any added items would not be shown. This was not an issue for me since the items would be added at the end of the List (outside the viewport) and when the user would scroll, the List would automatically be redrawn and the new items would be displayed. Perhaps in your situation you can force the redraw if need be.
When all items had been loaded I could then remove the event listener and return to the normal behavior of the List component.

Flex Spark List scroll to bottom when new data is added

I have a Spark List (spark.components.List) backed by an ArrayCollection for its dataProvider. The List has a vertical scrollbar when there's too many rows to display. What I want is when a new row is added to the List for it to scroll to the bottom to show that new row.
I've tried calling List's ensureIndexIsVisible from a listener on the ArrayCollection. This doesn't work because the List hasn't yet fully rendered the new row. It will either scroll to the second from the last row, or throw the exception:
Error: invalidIndex
at spark.layouts.supportClasses::LinearLayoutVector/start()[E:\dev\4.0.0\frameworks\projects\spark\src\spark\layouts\supportClasses\LinearLayoutVector.as:893]
at spark.layouts.supportClasses::LinearLayoutVector/getBounds()[E:\dev\4.0.0\frameworks\projects\spark\src\spark\layouts\supportClasses\LinearLayoutVector.as:1117]
at spark.layouts::VerticalLayout/getElementBounds()[E:\dev\4.0.0\frameworks\projects\spark\src\spark\layouts\VerticalLayout.as:852]
at spark.layouts.supportClasses::LayoutBase/http://www.adobe.com/2006/flex/mx/internal::getScrollPositionDeltaToElementHelper()[E:\dev\4.0.0\frameworks\projects\spark\src\spark\layouts\supportClasses\LayoutBase.as:1367]
at spark.layouts.supportClasses::LayoutBase/getScrollPositionDeltaToElement()[E:\dev\4.0.0\frameworks\projects\spark\src\spark\layouts\supportClasses\LayoutBase.as:1348]
at spark.components::List/ensureIndexIsVisible()[E:\dev\4.0.0\frameworks\projects\spark\src\spark\components\List.as:2105]
I've made sure that my listener is added to the ArrayCollection after setting List's dataProvider. The hope is that I'd be calling ensureIndexIsVisible after the List got a chance to process the new row. My guess is that the List doesn't render the row until some redraw event that occurs later (after I've called ensureIndexIsVisible).
I've also tried specifying a VerticalLayout and setting its verticalScrollPosition to an overly large number (like 99999). This has the same problem - it scrolls to the second from the last row.
So, is there a way to scroll to a newly added row in a Spark List? The only I could find on the Internet is this poorly formatted flexcoders thread.
Instead of extending List in order to override updateDisplayList, I added a listener to the FlexEvent.UPDATE_COMPLETE on my List component and it works just fine for me.
My solution was to extend List and override updateDisplayList, which works:
protected override function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
// call ensureIndexIsVisible here
}
See discussion on Adobe's forums.
I found that when ensureIndexIsVisible is run directly after an insert to a list dataProvider it does not accurately pull the position of the last item in the list. This looks to be because the binding has not had a chance to update the list. The fastest solution for me was to run a callLater on ensureIndexIsVisible.
_acGuestBookMessages.addItemAt(obj, _acGuestBookMessages.length);
var arr:Array = [_acGuestBookMessages.length-1];
callLater(lstGuestBookMessages.ensureIndexIsVisible, arr);
Want to scroll to bottom of an updating spark list and
ensureIndexIsVisible is too much undocumented trouble?
I got this to work by dealing with the physical height of dataGroup property on List...
function newMessage(...)
{
messages.addItemAt(msg, messages.length);
messages.refresh();
scrollToBottom();
}
function scrollToBottom():void
{
messagesList.addEventListener("updateComplete", scrollUpdate);
}
private function scrollUpdate(e:Event):void
{
messagesList.removeEventListener("updateComplete", scrollUpdate);
messagesList.dataGroup.verticalScrollPosition = messagesList.dataGroup.contentHeight - messagesList.dataGroup.height;
}
if(itemsList && itemsList.scroller &&
itemsList.scroller.verticalScrollBar &&
itemsList.scroller.verticalScrollBar.visible){
itemsList.scroller.verticalScrollBar.value =
itemsList.scroller.verticalScrollBar.maximum;
}
Was having the same problem, this works for me -
if(chatCollection.length > 0)
{
chatList.validateNow();
chatList.ensureIndexIsVisible(chatCollection.length - 1);
}

Resources