B4J Question [ABMaterial] How to reload (refresh) hole pages?

johnerikson

Active Member
Licensed User
Longtime User
I use the [ABMaterial] in a new Webapp
I work with imaging and I want to use the same page for different functions. By changing different function parameters to change the presentation of the page, gives no result. I've tried using page. refresh but does not work.

What shall I do?
 

Harris

Expert
Licensed User
Longtime User
@codie01 Waw, looks great! Just a hint:

Make use of the responsive power ABMaterial has: e.g. for the tool cards, make the cells something like AddCellOS(6, 0,0,0,12,4,2). This will result in somthing like the attachment on a phone (all 'cards' are under each other). Also, center the image using a cell theme with center alignment.

It's hard to wrap one's head around this to make it responsive. We (old guys) think in desktop terms and don't see beyond this.
We are learning with your help...

Thanks.
 
Upvote 0

alwaysbusy

Expert
Licensed User
Longtime User
It's best to think about it in plain English:

AddCellOS(6, 0,0,0,12,4,2) means:

Make 6 cells,

On small screens (phone), the cell needs to occupy all 12 blocks (12/1 = 12)
On medium screens (tablet), the cell needs to occupy one third of the blocks (12/3 = 4)
On large screens (desktop), the cell needs to occupy one sixth of the blocks (12/6 = 2)

So the result will be like this on the devices:

Phone:

| 01 (12 blocks) | (total = 12 blocks, so wrap)
| 02 (12 blocks) | (total = 12 blocks, so wrap)
| 03 (12 blocks) | (total = 12 blocks, so wrap)
| 04 (12 blocks) | (total = 12 blocks, so wrap)
| 05 (12 blocks) | (total = 12 blocks, so wrap)
| 06 (12 blocks) | (total = 12 blocks, so wrap)

Tablet:

| 01 (4 blocks) | 02 (4 blocks) | 03 (4 blocks) | (total = 12 blocks, so wrap)
| 04 (4 blocks) | 05 (4 blocks) | 06 (4 blocks) | (total = 12 blocks, so wrap)

Desktop:

| 01 (2 blocks) | 02 (2 blocks) | 03 (2 blocks) | 04 (2 blocks) | 05 (2 blocks) | 06 (2 blocks) | (total = 12 blocks, so wrap)
 
Upvote 0

johnerikson

Active Member
Licensed User
Longtime User
@codie01 about this in the manual:



This is not entirely true. Those components can be changed or refreshed, just NOT IN BuildPage(). You can change them e.g. in the WebSocket_Connected() or any other method like a button_Clicked. Just grab the component you created in BuildPage() and change it:

e.g.

in BuildPage():

B4X:
' create the input fields
Dim inp2 As ABMInput
inp2.Initialize(page, "inp2", ABM.INPUT_TEXT, "Volledig adres", False, "orange")
inp2.IconName = "mdi-maps-local-attraction"
inp2.Visibility = ABM.VISIBILITY_HIDE_ALL
page.CellR(0,1).AddComponent(inp2)

and in myButton_Clicked(Target as String):

B4X:
Dim inp2 As ABMInput = page.Component("inp2")
inp2.IconName="mdi-another-icon"
inp2.refresh


This method works for example with Card Component, that I use successfully, but not on a ABMImageSlider components.

I use this code in BuildPage():

B4X:
    'cSliders is a global public var
    'lDirs is a List whith images from a directory

    Dim sImg, sid As String
    cSliders.Initialize(page, "Slide1", "")
    For i = 0 To iCountRow - 1
        sImg = lDirs.Get(i)
        cSliders.AddSlideImage("../EasyImageslider/images/" & sImg, "", "Exempel", ABM.IMAGESLIDER_LEFT)
        cSliders.Visibility = ABM.VISIBILITY_HIDE_ALL
    Next
    page.Cell(2,1).AddComponent( cSliders)
    ABMShared.BuildFooter(page)

and this code in LoadImage():
B4X:
     Dim Sliders As ABMImageSlider = page.Component("Slide1") ' show this error No component found with id Slide1
    For i = 0 To iCountRow - 1
        sImg = lDirs.Get(i)
        Sliders.AddSlideImage("../EasyImageslider/images/" & sImg, "", "slogan", ABM.IMAGESLIDER_LEFT)
        Sliders.Visibility = ABM.VISIBILITY_ALL
        Sliders.Refresh
    Next

A recurring difficulty succeeding with dynamic pages.

In this case Dim Sliders As ABMImageSlider = page.php Component ("Slide1") does not recognize the component "Slide1" and generating an error!

Probably I must solve this in a different way, but how?
 
Upvote 0

alwaysbusy

Expert
Licensed User
Longtime User
Try removing cSliders from the global public vars (there is no need at all to do this). Just tried this in the demo and it works:

In BuildPage()

B4X:
...
    ' create slider
    Dim slider As ABMImageSlider
    slider.Initialize(page, "slider", "")
   
    ' add images
    slider.AddSlideImage("../images/slider1.jpg","This is our big Title!", "Here's a small slogan.", ABM.IMAGESLIDER_LEFT)
    slider.AddSlideImage("../images/slider2.jpg","This is our big Title!", "Here's a small slogan.", ABM.IMAGESLIDER_CENTER)
    slider.AddSlideImage("../images/slider3.jpg","This Is our big Title!", "Here's a small slogan.", ABM.IMAGESLIDER_RIGHT)
    slider.AddSlideImage("../images/slider4.jpg","This is our big Title!", "Here's a small slogan.", ABM.IMAGESLIDER_CENTER)
   
    page.Cell(2,1).AddComponent(slider)
    ...

In WebSocket_Connected() <---- where you should put your loadImages() method

B4X:
...
    ' connect our page with the websocket   
    page.SetWebSocket(ws)
    ' Prepare the page IMPORTANT!
    page.Prepare   
   
    Dim slider As ABMImageSlider = page.Component("slider")
    Log(slider.ID)
 
Upvote 0

johnerikson

Active Member
Licensed User
Longtime User
Try removing cSliders from the global public vars (there is no need at all to do this). Just tried this in the demo and it works:

In BuildPage()

B4X:
...
    ' create slider
    Dim slider As ABMImageSlider
    slider.Initialize(page, "slider", "")
  
    ' add images
    slider.AddSlideImage("../images/slider1.jpg","This is our big Title!", "Here's a small slogan.", ABM.IMAGESLIDER_LEFT)
    slider.AddSlideImage("../images/slider2.jpg","This is our big Title!", "Here's a small slogan.", ABM.IMAGESLIDER_CENTER)
    slider.AddSlideImage("../images/slider3.jpg","This Is our big Title!", "Here's a small slogan.", ABM.IMAGESLIDER_RIGHT)
    slider.AddSlideImage("../images/slider4.jpg","This is our big Title!", "Here's a small slogan.", ABM.IMAGESLIDER_CENTER)
  
    page.Cell(2,1).AddComponent(slider)
    ...

In WebSocket_Connected() <---- where you should put your loadImages() method

B4X:
...
    ' connect our page with the websocket  
    page.SetWebSocket(ws)
    ' Prepare the page IMPORTANT!
    page.Prepare  
  
    Dim slider As ABMImageSlider = page.Component("slider")
    Log(slider.ID)



Yes, it works once on startup. After the images are changed out, I only get grey areas but no pictures are visible!
I think the new images not load in repeated runs, the first images from start is left!

The desired function is:
The user can has thousands of images that are categorized. A choice in the Treeview class site can be made
which then enable ABMImageSlider class.
The point is that it should also work if new categories is selected.
 
Upvote 0

johnerikson

Active Member
Licensed User
Longtime User
ImageSlider is not an object created to hold thousands of images. This is an object to hold a limited fixed number of slides. (e.g. there is no RemoveSlideImage method). Put in a feature request on the feedback app for such a new control.

There has been a misunderstanding. After a choice of one category, it might be 4 to 50 images to be run in Imageslider. Imageslider shall not deal with the 1000 images, it is managed in in a database where they are stored as a Blob. A column in the database contains a search key for each category.


A general issue

Do you think I am wrong to develop a WEBapp built on ADMaterial which is not intended for home pages.

EasyMedia is an WEBapp for administration, editing and presentation of images over all. I have developed a file server that communicates with the SmartPhone for upload of images stored in a database. A B4A app in mobiles handles categorization and storage premises. If access is available to fileserver data sends and been stored in the database. Multiple users can be online when a logon, and the mobile phone's id is the unique keys. As a result, full dynamics on WEB app then the data in the database is changing all the time (real-time). Images is to be moved between categories, delete, rename, editing categories in the tree etc. etc.

I have already developed a WEB-app on B4J which has many of these features, but the design is difficult and it is a hassle with. html and js and css. That is why I jumped on the ABMaterials framework. Personally I believe that I will win to continue with the ABM , only I get past some problems come across.

In this case (ImageSLider), I'm thinking I using a timer to display images in sequence until another solution is available! I miss a click_event handler for images! Is there such a?


If I can develop such an app, it should be a good demo of a technical business application! Maybe I can help with that if I succeed!


What do you think, SHALL I MOVING ON?
 
Upvote 0

alwaysbusy

Expert
Licensed User
Longtime User
Image does not have a clicked event because when MaterialBoxed is set it already has a function: blow up the image full size in the browser. I could add an Image_Clicked event for images where the MaterialBox property is false. Please add a feature request to feedback, otherwise this will get lost in the forum conversation!

I have been thinking about creating a 'ABMInfiniteContainer' component which would mimic the behaviour you see in facebook and twitter. When the user scrolls, the next 'batch' of items (images) will be loaded. Could be helpful in your story.

It would help if you could show me an example of what you want to re-make. It appears you've changed your concept since I've showed you how to reload images and I'm getting confused :)

ABMaterial is a work in progress and I think we've come a long way since its initial release. But there will always be things someone want to create, and ABMaterial is not ready for yet. That is where feature request come in...
 
Last edited:
Upvote 0

johnerikson

Active Member
Licensed User
Longtime User
I understand that it is difficult to read what I write in English. I am impressed by your patience.

The project I'm into is a relatively complex concept. It is based on the fact that those who get tired of having to manually keep track of all the photos and videos across time and space to get help to them with it.

In order for it to work successful, images with text and coordinates stores in a database on the server, immediately if the user is online. This means that data changes in the database happens all the time. For example, if the user of the Mobile adds a new category, taking pictures and maybe write a text to the image, so if the user is online the database will updates immediately.

The server shall allways be online. When the user logs on or is logged on, in this moment a number of pages had to be dynamic, that for be able to load and presentation the changes from the database.

For example, in the category tree the user select category to perform any of the following:
See att. images (.pptx file)
  1. Looking at photos in date order (Is dynamic)
  2. starting a imageslider (Not dynamic)
  3. Edit image content (delete, rotate, move) to add a new category in the tree to move the images to the new category (Dynamic)
  4. Edit the text to the image
  5. Edit the treeview content (Not dynamic)
in order to view all operations on the screen requires that the changes can be loaded directly, otherwise, the application must be restarted for displaying changes. It would affect other users and not come across professionally.

As a result of all of this I which get back to the title “Question B4J-[ABMaterial] How to reload (refresh) hole pages?” Or even better oage.rebuild(className), but I understand that there are problems to fix it when is not already exist! But nothing is impossible!

I put some suggestions as wishes, then I can see reactions to them!
A Image_Clicked is good I put it as wishes to.
A query, when a user is logging out, is pages reloading when the user login again?
 

Attachments

  • Presentation.zip
    424.7 KB · Views: 256
Upvote 0

johnerikson

Active Member
Licensed User
Longtime User
I have solved my problem with reload on ABMTreeTable och ABMImageSlider.
No need to waste hours to think about this now because I seem to be quite alone about which thise functions. If anyone is interested in what I have done, please contact me!
In brief:
By building new content of the tree data from the database and then replace it to the .html file and run a new redirect to current class. Works great in my case, but becomes a maintenance problem. If format changes of the TreeTable structure in a new version, I will have to make changes, but I accept it.

I've done the same with ABMImageSlider, which also works great and is less sensitive to changes after that I only do replace with lists of Images!
Now I'm keep on working:)
 
Upvote 0

alwaysbusy

Expert
Licensed User
Longtime User
@johnerikson Thanks for the presentation! I finally get it :)

1. Treeview problem is indeed a bug. It appears you cannot refresh a treeview correctly. I will have to add multiple methods to add, remove, refresh a row. Going to be a tough one to crack, but we'll get there...
2. Just read your post, but I think I will add some methods anyway to ABMImageSlider so you will be able to do this:

B4X:
Sub btn1_Clicked(Target As String)
    Dim slider As ABMImageSlider = page.Component("slider")
    ' clear the slider
    slider.Clear
    ' add the new images
    slider.AddSlideImage("../images/slider5.jpg","This is our big Title!", "Here's a small slogan.", ABM.IMAGESLIDER_LEFT)
    slider.AddSlideImage("../images/slider6.jpg","This is our big Title!", "Here's a small slogan.", ABM.IMAGESLIDER_CENTER)
    slider.AddSlideImage("../images/slider7.jpg","This Is our big Title!", "Here's a small slogan.", ABM.IMAGESLIDER_RIGHT)
    slider.AddSlideImage("../images/slider8.jpg","This is our big Title!", "Here's a small slogan.", ABM.IMAGESLIDER_CENTER)
    slider.AddSlideImage("../images/slider9.jpg","This is our big Title!", "Here's a small slogan.", ABM.IMAGESLIDER_LEFT)
    slider.AddSlideImage("../images/slider10.jpg","This is our big Title!", "Here's a small slogan.", ABM.IMAGESLIDER_CENTER)
    slider.Refresh
End Sub
 
Last edited:
Upvote 0

alwaysbusy

Expert
Licensed User
Longtime User
@johnerikson I'm starting to get there :)

Adding lines in the tree, whithout having to refresh the whole tree:

upload_2016-2-9_17-5-16.png


The ABMImageSlider can now also reload other images.

Still some work on the tree...
 
Upvote 0

johnerikson

Active Member
Licensed User
Longtime User
It’s good if you can add new nodes in the tree. But move, delete , rename are other needs.
All needs work in my current solution!

I don't think you come away from reload this page (for example TreeTable) to update the dynamic data. If multiple users access the same database, a refresh of the page's content is to be necessary. Otherwise, all will not be in phase with current data.

I have tested and concluded that for good interactive environment works best with the solution I have made simple. This is because the user
always gets an updated index.html with the latest content from the database.

It’s transparent for the programmer if you added in the your lib.

The only thing to do with the TreeTable is that outside the static part is to use the same code you have to build the tree to write in a temp file. Then do a replace of it between two identifiers (Begin and end) in .html-file. In TreeTables. html-file this already exists (<tbody> and </tbody>)

Below what to I do:

B4X:
Sub ReLoadTreeView
    'Read from Database
    InitData ' Results is global
   
    'Open  block.html (as tmp - file)
    otw.Initialize(File.OpenOutput(Main.PG_sAppPath & "\EasyTreeview", "block.html", False)) ' otw is global
    
    'Add rows to block.html  In my case i have Made a custom algoritm to sort and build html data.  Not all variants , four as I needed
    For i = 1 To igCount' 
        ReAddRow( iLevel(sTVadmIDX(i)), iId(sTVadmIDX(i)),  iParantID(sTVadmIDX(i)), False, sLabel(sTVadmIDX(i)))' variabels is global
    Next
    otw.Close
   
    'Replace Block into a new tmp.html - file  Repace directly to current EasyTreeview.html does not work!
    WebUtils.ReplaceBlock("EasyTreeview.html", "block.html", "EasyTreeviewNew.html", "<tbody>", "</tbody>")
   
    'Copy the tmp - file to current EasyTreeview.html - file
    File.Copy(Main.PG_sAppPath & "\EasyTreeview", "EasyTreeviewNew.html", Main.PG_sAppPath & "\EasyTreeview", "EasyTreeview.html")
   
    'Run the updated .html - file
    ABMShared.NavigateToPage(ws,"../EasyTreeview/EasyTreeview.html")
End Sub
Most of abowe code can be in the lib!
Currently I am satisfied as function now!
 
Upvote 0

alwaysbusy

Expert
Licensed User
Longtime User
Thank you for sharing, but ABMTreeTable has to work like all the other components in ABMaterial, dynamic. (Imagine you have a tree with thousands of leafs and you change one, then not the whole tree, but just that leaf has to be send over and refreshed). So I still need to correct it. Add and rename is finished (refresh works correctly now), working on delete. I'll see what I can do about move.

I don't think my changes will have an impact on your code so you can still use it, as long as you do not do a refresh (that is where the magic happens in my lib).
 
Last edited:
Upvote 0

alwaysbusy

Expert
Licensed User
Longtime User
No, it does not (and neither do all other ABMaterial components). Once loaded, it is only manipulated in the users browser. I suppose Erel's push system could be used if you want all trees of all users to be in sync, however I think that would be overkill. Better create a button 'refresh' on the page that reloads the whole tree for such events.
 
Upvote 0

johnerikson

Active Member
Licensed User
Longtime User
Wow, I do not understand how the code under that button that allows refresh on the whole page looks like?
That's what i basically ask for!
 
Upvote 0

alwaysbusy

Expert
Licensed User
Longtime User
Something like this in WebSocketed_Connected() or under a button_clicked():

Dim tree as ABMTreeTable = page.component("mytree")
tree.clear

' loading the new rows (like in BuildPage in the demo)

Disadvantage of this, you loose the information about what branches were expanded before the refresh.

Just changing the braches/leafes without a complete refresh can leave the state of the other branches untouched.
 
Upvote 0

johnerikson

Active Member
Licensed User
Longtime User
The effect is not what I was expecting. Unless there is something I missed or do not understand!
I think we have different starting points to come to the same solution.
In my case, not me as developer or any company is the one that adds data for use in the app. It is the users themselves as either through mobile or with some WEBapp adds changes to the database. Therefore, my solution is to be able to add new data from the database without restarting the WEBapp. In the demo there are no external data from files, directories or database which anyone can changes the data outside the WEBapp in the meantime the WEBapp is running .

I think that is the main different of starting point for thinking!

Don’t worry, be happy!

I had the solution. The point of using ABMaterial is to create better looking design for less time and avoid html and js, which it makes satisfactory.
Therefore, it is not such a big problem to have custom solutions to special features.
 
Upvote 0

alwaysbusy

Expert
Licensed User
Longtime User
I'm not really convinced my new version can not be a solution for your problem. Here is a video demonstating loading, adding, changing, removing and moving nodes. All is done in the browser and synced with the server (e.g. to change a database, to move a picture to another folder etc...)

For the demo, imagine the following would be done in a real app:
  1. LoadTree: Here you would clear the current tree and load new rows from e.g. a database. This method can be repeated as much as yoy like, loading new data. So every user that opens the browser sees his/her 'tree' and can make changes to it.
  2. Pressing +: we could show a modal sheet, let the user enter its data and when he presses on save, we save the new data in the database and add the new node to the tree
  3. Pressing Edit: we could show a modal sheet with the data from the node, let the user edit it and when he presses on save, we update the new data in the database and update the new node to the tree
  4. Pressing Delete: we delete the data in our database (the node is deleted automatically)
  5. Moving a node: you can set what nodes can be dropped where (in the video trying to drop a 'hour' child node on a done group is not accepted). Also an event is raised so we can make the needed changes to the database, move a file to another folder, etc...

This is the new code in the demo app:

B4X:
'Class module
Sub Class_Globals
    Private ws As WebSocket 'ignore
    ' will hold our page information
    Public page As ABMPage
    ' page theme
    Private theme As ABMTheme
    ' to access the constants
    Private ABM As ABMaterial 'ignore 
    ' name of the page, must be the same as the class name (case sensitive!)
    Public Name As String = "CompTreeTablePage"  '<-------------------------------------------------------- IMPORTANT
    ' name of the app, same as in ABMApplication
    Public AppName As String = "demo"          '<-------------------------------------------------------- IMPORTANT 
 
    ' your own variables
    Dim myToastId As Int 
    'Dim PDFName As String 
    Dim PlusParents As Map
 
    Type PlusStructure (Prefix As String, NextValue As Int)
End Sub

'Initializes the object. You can add parameters to this method if needed.
Public Sub Initialize
    ' build the local structure IMPORTANT!
    BuildPage
End Sub

Private Sub WebSocket_Connected (WebSocket1 As WebSocket)
    Log("Connected")
    ws = WebSocket1 
 
    If ABMShared.NeedsAuthorization Then
        If ws.Session.GetAttribute2("IsAuthorized", "") = "" Then
            ABMShared.NavigateToPage(ws, "../")
            Return
        End If
    End If
    ' connect our page with the websocket 
    page.SetWebSocket(ws) 
    ' Prepare the page IMPORTANT!
    page.Prepare 
 
    PlusParents.Initialize
 
    LoadTree     
End Sub

' imagine this comes from a database, for the demo's purpose it is hard coded
' you can call this method with a different query every time (e.g. depending on the user)
Sub LoadTree()
    DateTime.DateFormat = "EEEE d MMMM yyyy"
    Dim view1 As ABMTreeTable = page.Component("view1")
    view1.Clear
    view1.AddRow(BuildLev1Header(1, "1", "", False, "Ypres"))
    view1.AddRow(BuildLev2Header1(2, "1_1", "1", False, "Menin Gate", DateTime.Date(DateTime.Now)))
    view1.AddRow(BuildLev3Header(3, "1_1_1", "1_1", False , "Hours", Array As String("HOURS")))
    view1.AddRow(BuildLev4Body1(4, "1_1_1_1", "1_1_1", False, "ABA", "Alain Bailleul", "05:00"))
    view1.AddRow(BuildLev4Body1(4, "1_1_1_2", "1_1_1", False, "JSM", "Jones Smith", "03:00"))
    view1.AddRow(BuildLev4Body1(4, "1_1_1_3", "1_1_1", False, "SBF", "Sindy Belfast", "03:00"))
    view1.AddRow(BuildLev3Footer1(3, "1_1_2", "1_1", True, "3 pers.", "11 hours"))
    view1.AddRow(BuildLev3Header(3, "1_1_3", "1_1", False, "Done",Array As String("DONE")))
    view1.AddRow(BuildLev4Body2(4, "1_1_3_1", "1_1_3", False, "First floor - swept"))
    view1.AddRow(BuildLev4Body2(4, "1_1_3_2", "1_1_3", False, "Second floor - vacuum-cleaned"))
    view1.AddRow(BuildLev3Footer2(3, "1_1_4", "1_1", True))
    view1.AddRow(BuildLev3Header(3, "1_1_5", "1_1", False, "Used devices",Array As String()))
    view1.AddRow(BuildLev4Body3(4, "1_1_5_1", "1_1_5", False, "Vacuum Cleaner", "2", "half a day"))
    view1.AddRow(BuildLev3Footer2(3, "1_1_6", "1_1", True))
    view1.AddRow(BuildLev3Header(3, "1_1_7", "1_1", False, "Result",Array As String()))
    view1.AddRow(BuildLev4Body3(4, "1_1_7_1", "1_1_7", False, "2 dust bins full", "2", "ton"))
    view1.AddRow(BuildLev3Footer2(3, "1_1_8", "1_1", True))
    view1.AddRow(BuildLev3Header(3, "1_1_9", "1_1", False, "Extra info",Array As String()))
    view1.AddRow(BuildLev4Body2(4, "1_1_9_1", "1_1_9", False, "Everything cleaned, vacuum cleaner needs to be repaired"))
    view1.AddRow(BuildLev3Footer2(3, "1_1_10", "1_1", True))
    view1.AddRow(BuildLev3Header(3, "1_1_11", "1_1", False, "Overview",Array As String()))
    view1.AddRow(BuildLev4Body4(4, "1_1_11_1", "1_1_11", False, Array As Int(30,50,70,80,100,140,170), Array As Int(100,120,180,150,190,100,70)))
    view1.AddRow(BuildLev3Footer2(3, "1_1_12", "1_1", True))
    view1.AddRow(BuildLev2Footer(2, "1_2", "1", True))
 
    PlusParents.Put("1_2", NewPlusStructure("1_1_1", 4))
     
    view1.AddRow(BuildLev2Header1(2, "1_3", "1", False, "Big Square", DateTime.Date(DateTime.Now)))
    view1.AddRow(BuildLev3Header(3, "1_3_1", "1_3", False , "Hours",Array As String("HOURS")))
    view1.AddRow(BuildLev4Body1(4, "1_3_1_1", "1_3_1", False, "SBF", "Sindy Belfast", "04:00"))
    view1.AddRow(BuildLev3Footer1(3, "1_3_2", "1_3", True, "1 pers.", "4 hours"))
    view1.AddRow(BuildLev3Header(3, "1_3_3", "1_3", False, "Done",Array As String("DONE")))
    view1.AddRow(BuildLev4Body2(4, "1_3_3_1", "1_3_3", False, "First floor - swept"))
    view1.AddRow(BuildLev4Body2(4, "1_3_3_2", "1_3_3", False, "Second floor - vacuum-cleaned"))
    view1.AddRow(BuildLev3Footer2(3, "1_3_4", "1_3", True))
    view1.AddRow(BuildLev3Header(3, "1_3_5", "1_3", False, "On map",Array As String()))
    view1.AddRow(BuildLev4Body5(4, "1_3_5_1", "1_3_5", False, 50.8500, 2.8833))
    view1.AddRow(BuildLev3Footer2(3, "1_3_6", "1_3", True))
    view1.AddRow(BuildLev2Footer(2, "1_4", "1", True))
 
    PlusParents.Put("1_4", NewPlusStructure("1_3_1", 2))
 
    view1.AddRow(BuildLev2Header1(2, "1_5", "1", False, "Swimming pool", DateTime.Date(DateTime.Now)))
    view1.AddRow(BuildLev3Header(3, "1_5_1", "1_5", False , "Hours",Array As String("HOURS")))
    view1.AddRow(BuildLev4Body1(4, "1_5_1_1", "1_5_1", False, "SBF", "Sindy Belfast", "04:00"))
    view1.AddRow(BuildLev3Footer1(3, "1_5_2", "1_5", True, "1 pers.", "4 hours"))
    view1.AddRow(BuildLev3Header(3, "1_5_3", "1_5", False, "Done",Array As String("DONE")))
    view1.AddRow(BuildLev4Body2(4, "1_5_3_1", "1_5_3", False, "First floor - swept"))
    view1.AddRow(BuildLev3Footer2(3, "1_5_4", "1_5", True))
    view1.AddRow(BuildLev3Header(3, "1_5_5", "1_5", False, "Used devices",Array As String()))
    view1.AddRow(BuildLev4Body3(4, "1_5_5_1", "1_5_5", False, "Vacuum Cleaner", "2", "half a day"))
    view1.AddRow(BuildLev3Footer2(3, "1_5_6", "1_5", True)) 
    view1.AddRow(BuildLev2Footer(2, "1_6", "1", True))
 
    PlusParents.Put("1_6", NewPlusStructure("1_5_1", 1))
 
    view1.AddRow(BuildLev2Header1(2, "1_7", "1", False, "Parking lot", DateTime.Date(DateTime.Now)))
    view1.AddRow(BuildLev3Header(3, "1_7_1", "1_7", False , "Hours",Array As String("HOURS")))
    view1.AddRow(BuildLev4Body1(4, "1_7_1_1", "1_7_1", False, "ABA", "Alain Bailleul", "05:00"))
    view1.AddRow(BuildLev4Body1(4, "1_7_1_2", "1_7_1", False, "JSM", "Jones Smith", "03:00"))
    view1.AddRow(BuildLev4Body1(4, "1_7_1_3", "1_7_1", False, "SBF", "Sindy Belfast", "03:00"))
    view1.AddRow(BuildLev3Footer1(3, "1_7_2", "1_7", True, "3 pers.", "11 hours"))
    view1.AddRow(BuildLev3Header(3, "1_7_3", "1_7", False, "Done",Array As String("DONE")))
    view1.AddRow(BuildLev4Body2(4, "1_7_3_1", "1_7_3", False, "First floor - swept"))
    view1.AddRow(BuildLev3Footer2(3, "1_7_4", "1_7", True))
    view1.AddRow(BuildLev3Header(3, "1_7_5", "1_7", False, "Result",Array As String()))
    view1.AddRow(BuildLev4Body3(4, "1_7_5_1", "1_7_5", False, "2 dust bins full", "2", "ton"))
    view1.AddRow(BuildLev3Footer2(3, "1_7_6", "1_7", True))
    view1.AddRow(BuildLev2Footer(2, "1_8", "1", True))
 
    PlusParents.Put("1_8", NewPlusStructure("1_7_1", 4))
 
    view1.AddRow(BuildLev1Footer(1, "2", "", True))
 
    view1.AddRow(BuildLev1Header(1, "3", "", False, "Brussels"))
    view1.AddRow(BuildLev2Header1(2, "3_1", "3", False, "Palace", DateTime.Date(DateTime.Now)))
    view1.AddRow(BuildLev3Header(3, "3_1_1", "3_1", False , "Hours",Array As String("HOURS")))
    view1.AddRow(BuildLev4Body1(4, "3_1_1_1", "3_1_1", False, "ABA", "Alain Bailleul", "05:00"))
    view1.AddRow(BuildLev4Body1(4, "3_1_1_2", "3_1_1", False, "SBF", "Sindy Belfast", "03:00"))
    view1.AddRow(BuildLev3Footer1(3, "3_1_2", "3_1", True, "2 pers.", "8 hours"))
    view1.AddRow(BuildLev3Header(3, "3_1_3", "3_1", False, "Done",Array As String("DONE")))
    view1.AddRow(BuildLev4Body2(4, "3_1_3_1", "3_1_3", False, "First floor - swept"))
    view1.AddRow(BuildLev4Body2(4, "3_1_3_2", "3_1_3", False, "Second floor - vacuum-cleaned"))
    view1.AddRow(BuildLev3Footer2(3, "3_1_4", "3_1", True))
 
    view1.AddRow(BuildLev2Footer(2, "3_2", "3", True))
 
    PlusParents.Put("3_2", NewPlusStructure("3_1_1", 3))
 
    view1.AddRow(BuildLev1Footer(1, "4", "", True))
     
    view1.Refresh
End Sub

Sub NewPlusStructure(Prefix As String, NextValue As Int) As PlusStructure
    Dim ps As PlusStructure
    ps.Initialize
    ps.Prefix = Prefix
    ps.NextValue = NextValue
    Return ps
End Sub

Private Sub WebSocket_Disconnected
    Log("Disconnected")
End Sub

Sub Page_ParseEvent(Params As Map)
    Dim eventName As String = Params.Get("eventname")
    Dim eventParams() As String = Regex.Split(",",Params.Get("eventparams"))
    If SubExists(Me, eventName) Then
        Params.Remove("eventname")
        Params.Remove("eventparams")
        Select Case Params.Size
            Case 0
                CallSub(Me, eventName)
            Case 1
                CallSub2(Me, eventName, Params.Get(eventParams(0)))                 
            Case 2
                If Params.get(eventParams(0)) = "abmistable" Then
                    Dim PassedTables As List = ABM.ProcessTablesFromTargetName(Params.get(eventParams(1)))
                    CallSub2(Me, eventName, PassedTables)
                Else
                    CallSub3(Me, eventName, Params.Get(eventParams(0)), Params.Get(eventParams(1)))
                End If
            Case Else
                ' cannot be called diretly, to many param
                CallSub2(Me, eventName, Params)             
        End Select
    End If
End Sub

public Sub BuildTheme()
    ' start with the base theme defined in ABMShared
    theme.Initialize("pagetheme")
    theme.AddABMTheme(ABMShared.MyTheme)
 
    ' add additional themes specific for this page
    theme.AddTreeTableTheme("view1")
    theme.TreeTable("view1").AddCellTheme("lev1default")
     
    theme.TreeTable("view1").Cell("lev1default").BackColor = ABM.COLOR_LIGHTBLUE
    theme.TreeTable("view1").Cell("lev1default").BackColorIntensity = ABM.INTENSITY_DARKEN3
    theme.TreeTable("view1").Cell("lev1default").ForeColor = ABM.COLOR_WHITE
    theme.TreeTable("view1").Cell("lev1default").IsEditable = True
     
    theme.TreeTable("view1").AddCellTheme("lev2default")
    theme.TreeTable("view1").Cell("lev2default").BackColor = ABM.COLOR_LIGHTBLUE
    theme.TreeTable("view1").Cell("lev2default").BackColorIntensity = ABM.INTENSITY_LIGHTEN2
     
    theme.TreeTable("view1").AddCellTheme("lev3default")
    theme.TreeTable("view1").Cell("lev3default").BackColor = ABM.COLOR_LIGHTBLUE
    theme.TreeTable("view1").Cell("lev3default").BackColorIntensity = ABM.INTENSITY_LIGHTEN4
         
    theme.TreeTable("view1").AddCellTheme("lev4default")
    theme.TreeTable("view1").Cell("lev4default").BackColor = ABM.COLOR_WHITE 
 
    theme.TreeTable("view1").AddCellTheme("lev4right")
    theme.TreeTable("view1").Cell("lev4right").BackColor = ABM.COLOR_WHITE 
    theme.TreeTable("view1").Cell("lev4right").Align = ABM.TABLECELL_HORIZONTALALIGN_RIGHT
 
    theme.TreeTable("view1").AddCellTheme("lev4orange")
    theme.TreeTable("view1").Cell("lev4orange").BackColor = ABM.COLOR_AMBER
    theme.TreeTable("view1").Cell("lev4orange").BackColorIntensity = ABM.INTENSITY_LIGHTEN3
 
    theme.TreeTable("view1").AddTreeIconColorTheme("white")
    theme.TreeTable("view1").TreeIconColor("white").ForeColor = ABM.COLOR_WHITE
 
    theme.AddChartTheme("chart1theme")
    theme.Chart("chart1theme").Serie(ABM.CHART_SERIEINDEX_C).LinePointStrokeWidthPx=8
    theme.Chart("chart1theme").Serie(ABM.CHART_SERIEINDEX_C).LineStrokeWidthPx=5 
 
    ' bluegray button
    theme.AddButtonTheme("bluegrey")
    theme.Button("bluegrey").BackColor = ABM.COLOR_BLUEGREY
    theme.Button("bluegrey").BackColorIntensity = ABM.INTENSITY_DARKEN1
End Sub

public Sub BuildPage()
    ' initialize the theme
    BuildTheme
 
    ' initialize this page using our theme
    page.InitializeWithTheme(Name, "/ws/" & AppName & "/" & Name, False, theme)
    page.ShowLoader=True
    page.PageTitle = "ABMTreeTable"
    page.PageDescription = "The tree table component " 
    page.PageHTMLName = "abmaterial-tree-table.html"
    page.PageKeywords = "ABMaterial, material design, B4X, B4J, SEO, framework, search engine optimization"
    page.PageSiteMapPriority = "0.50"
    page.PageSiteMapFrequency = ABM.SITEMAP_FREQ_MONTHLY
    page.NeedsChart=True ' needed because we're going to add charts in our tree later
    page.NeedsGoogleMap=True ' needed because we're going to add google maps in our tree later
         
    ' adding a navigation bar
    ABMShared.BuildNavigationBar(page, "ABMTreeTable", "../images/logo.png", "", "Controls", "ABMTreeTable")
         
    ' create the page grid
    page.AddRows(6,True, "").AddCells12(1,"")
    page.BuildGrid 'IMPORTANT once you loaded the complete grid AND before you start adding components
 
    ' add paragraph 
    page.CellR(1,1).AddComponent(ABMShared.BuildParagraph(page,"par1","With the ABMTreeTable component you can build collapsable tree structures.  You can even use them to create simple reports (see the ABMPrint module).") )
 
    ' build an empty example tree 
    Dim view1 As ABMTreeTable
    view1.Initialize(page, "view1", False, "view1", 20, Array As String("lev1default", "lev2default", "lev3default", "lev4default"), 24)
    view1.CollapseTooltip = "Close"
    view1.ExpandTooltip = "Open" 
     
    page.CellR(1,1).AddComponent(view1)
 
End Sub

Sub BuildLev1Header(depth As Int, treeRowId As String, treeRowParentId As String, isFooter As Boolean, Stad As String) As ABMtreeTableRow
    Dim l As ABMtreeTableRow
    l.Initialize(depth, treeRowId, treeRowParentId, isFooter, "", "white") 
    Dim cell1 As ABMTreeTableCell
    cell1.Initalize("v1_" & treeRowId & "c1", Stad, 1,24, False, "")
    l.cells.Add(cell1)
    Return l
End Sub

Sub BuildLev1Footer(depth As Int, treeRowId As String, treeRowParentId As String, isFooter As Boolean) As ABMtreeTableRow
    Dim l As ABMtreeTableRow
    l.Initialize(depth, treeRowId, treeRowParentId, isFooter, "", "white") 
    Dim cell1 As ABMTreeTableCell
    cell1.InitalizeAsIcon("v1_" & treeRowId & "c1", "{NBSP}", 3,3, "")
    l.cells.Add(cell1)
    Return l
End Sub

Sub BuildLev2Header1(depth As Int, treeRowId As String, treeRowParentId As String, isFooter As Boolean, Straat As String, Datum As String) As ABMtreeTableRow
    Dim l As ABMtreeTableRow
    l.Initialize(depth, treeRowId, treeRowParentId, isFooter, "", "") 
    Dim cell1 As ABMTreeTableCell
    cell1.Initalize("v1_" & treeRowId & "c1", Straat, 2,12, False, "")
    l.cells.Add(cell1)
    Dim cell2 As ABMTreeTableCell
    cell2.Initalize("v1_" & treeRowId & "c2", Datum, 14,10, False, "")
    l.cells.Add(cell2)
    Return l
End Sub

Sub BuildLev2Footer(depth As Int, treeRowId As String, treeRowParentId As String, isFooter As Boolean) As ABMtreeTableRow
    Dim l As ABMtreeTableRow
    l.Initialize(depth, treeRowId, treeRowParentId, isFooter, "", "") 
    Dim cell1 As ABMTreeTableCell
    cell1.InitalizeAsIcon("v1_" & treeRowId & "c1", "mdi-content-add", 4,3, "")
    l.cells.Add(cell1)
    Return l
End Sub

Sub BuildLev3Header(depth As Int, treeRowId As String, treeRowParentId As String, isFooter As Boolean, soort As String, DragDropNames As List) As ABMtreeTableRow
    Dim l As ABMtreeTableRow
    l.Initialize(depth, treeRowId, treeRowParentId, isFooter, "", "") 
    l.SetCanReceiveDropsFrom(DragDropNames)
    Dim cell1 As ABMTreeTableCell
    cell1.Initalize("v1_" & treeRowId & "c1", soort, 3,21, False, "")
    l.cells.Add(cell1)
    Return l
End Sub

Sub BuildLev3Footer1(depth As Int, treeRowId As String, treeRowParentId As String, isFooter As Boolean, pers As String, time As String) As ABMtreeTableRow
    Dim l As ABMtreeTableRow
    l.Initialize(depth, treeRowId, treeRowParentId, isFooter, "", "") 
    Dim cell1 As ABMTreeTableCell
    cell1.Initalize("v1_" & treeRowId & "c1", pers, 4,3, False, "")
    l.cells.Add(cell1)
    Dim cell2 As ABMTreeTableCell
    cell2.Initalize("v1_" & treeRowId & "c2", time, 16,4, False, "")
    l.cells.Add(cell2)
    Return l
End Sub

Sub BuildLev3Footer2(depth As Int, treeRowId As String, treeRowParentId As String, isFooter As Boolean) As ABMtreeTableRow
    Dim l As ABMtreeTableRow
    l.Initialize(depth, treeRowId, treeRowParentId, isFooter, "", "") 
    Dim cell1 As ABMTreeTableCell
    cell1.Initalize("v1_" & treeRowId & "c1", "{NBSP}", 4,3, False, "")
    l.cells.Add(cell1)
    Return l
End Sub

Sub BuildLev4Body1(depth As Int, treeRowId As String, treeRowParentId As String, isFooter As Boolean, Code As String, Naam As String, Hours As String) As ABMtreeTableRow
    Dim l As ABMtreeTableRow
    l.Initialize(depth, treeRowId, treeRowParentId, isFooter, "HOURS", "") 
    Dim cell1 As ABMTreeTableCell
    cell1.Initalize("v1_" & treeRowId & "c1", Code, 4,3, False, "")
    l.cells.Add(cell1)
    Dim cell2 As ABMTreeTableCell
    cell2.Initalize("v1_" & treeRowId & "c2", Naam, 8,6, False, "")
    l.cells.Add(cell2)
    Dim cell3 As ABMTreeTableCell
    cell3.Initalize("v1_" & treeRowId & "c3", Hours, 15,2, False, "")
    l.cells.Add(cell3)
 
    Dim cell4 As ABMTreeTableCell
    Dim btnEdit As ABMButton
    btnEdit.InitializeFloating(page, treeRowId, "mdi-action-visibility", "")
    btnEdit.size = ABM.BUTTONSIZE_SMALL
    cell4.InitalizeAsComponent("v1_" & treeRowId & "cEdit", btnEdit, "btnEdit", 18,1, "")
    l.cells.Add(cell4)
     
    Dim cell5 As ABMTreeTableCell
    Dim btnDelete As ABMButton
    btnDelete.InitializeFloating(page, treeRowId, "mdi-action-delete", "")
    btnDelete.size = ABM.BUTTONSIZE_SMALL
    cell5.InitalizeAsComponent("v1_" & treeRowId & "cDelete", btnDelete, "btnDelete", 19,1, "")
    l.cells.Add(cell5)
     
    Return l
End Sub

Sub BuildLev4Body2(depth As Int, treeRowId As String, treeRowParentId As String, isFooter As Boolean, Artikel As String) As ABMtreeTableRow
    Dim l As ABMtreeTableRow
    l.Initialize(depth, treeRowId, treeRowParentId, isFooter, "DONE", "") 
    Dim cell1 As ABMTreeTableCell
    cell1.Initalize("v1_" & treeRowId & "c1", Artikel, 4,19, False, "")
    l.cells.Add(cell1)
    Return l
End Sub

Sub BuildLev4Body3(depth As Int, treeRowId As String, treeRowParentId As String, isFooter As Boolean, Artikel As String, aantal As String, eenheid As String) As ABMtreeTableRow
    Dim l As ABMtreeTableRow
    l.Initialize(depth, treeRowId, treeRowParentId, isFooter, "", "") 
    Dim cell1 As ABMTreeTableCell
    cell1.Initalize("v1_" & treeRowId & "c1", Artikel, 4,9, False, "")
    l.cells.Add(cell1)
    Dim cell2 As ABMTreeTableCell
    cell2.Initalize("v1_" & treeRowId & "c2", aantal, 15,2, False, "lev4right")
    l.cells.Add(cell2)
    Dim cell3 As ABMTreeTableCell
    cell3.Initalize("v1_" & treeRowId & "c3", eenheid, 17,3, False, "lev4orange")
    l.cells.Add(cell3)
    Return l
End Sub

Sub BuildLev4Body4(depth As Int, treeRowId As String, treeRowParentId As String, isFooter As Boolean, values1 As List, values2 As List) As ABMtreeTableRow
    ' create a line chart
    Dim chart1 As ABMChart
    chart1.Initialize(page, treeRowId, ABM.CHART_TYPELINE, ABM.CHART_RATIO_GOLDENSECTION, "chart1theme")
     
    ' add the labels
    chart1.Labels.AddAll(Array As String("Mon", "The", "Wed", "Thu", "Fri", "Sat", "Sun"))
    ' set some options
    chart1.OptionsLine.FullWidth=True
    chart1.OptionsLine.ChartPaddingRight=60 ' because we set fullwidth, we have to adjust so the final label also fits
    chart1.OptionsLine.Serie(ABM.CHART_SERIEINDEX_A).LineSmooth = ABM.CHART_LINESMOOTH_NONE
    chart1.OptionsLine.Serie(ABM.CHART_SERIEINDEX_C).LineSmooth = ABM.CHART_LINESMOOTH_SIMPLE
    chart1.OptionsLine.Serie(ABM.CHART_SERIEINDEX_C).ShowArea = True
 
    ' add some series 
    Dim SerieA As ABMChartSerie
    SerieA.InitializeForLine(ABM.CHART_SERIEINDEX_A)
    SerieA.Values.AddAll(values1)
    chart1.AddSerie(SerieA)
 
    Dim SerieC As ABMChartSerie
    SerieC.InitializeForLine(ABM.CHART_SERIEINDEX_C)
    SerieC.Values.AddAll(values2)
    chart1.AddSerie(SerieC)
 
    Dim l As ABMtreeTableRow
    l.Initialize(depth, treeRowId, treeRowParentId, isFooter, "", "") 
    Dim cell1 As ABMTreeTableCell
    cell1.InitalizeAsComponent("v1_" & treeRowId & "c1", chart1, "chart1", 4,19, "")
    l.cells.Add(cell1)
    Return l
End Sub

Sub BuildLev4Body5(depth As Int, treeRowId As String, treeRowParentId As String, isFooter As Boolean, lat As Double, lng As Double) As ABMtreeTableRow
    ' create the google map
    Dim gm1 As ABMGoogleMap
    gm1.Initialize(page, treeRowId, lat, lng, 15, 350, ABM.GOOGLEMAPTYPE_ROADMAP)
    gm1.Draggable = False
    gm1.HasMapTypeControl = True 
    gm1.HasStreetViewControl = True
    gm1.HasZoomControl = True
    gm1.AddMapType(ABM.GOOGLEMAPTYPE_ROADMAP)
    gm1.AddMapType(ABM.GOOGLEMAPTYPE_TERRAIN)
    gm1.AddMapType(ABM.GOOGLEMAPTYPE_HYBRID)
    gm1.AddMapType(ABM.GOOGLEMAPTYPE_SATELLITE)
 
    Dim l As ABMtreeTableRow
    l.Initialize(depth, treeRowId, treeRowParentId, isFooter, "", "") 
    Dim cell1 As ABMTreeTableCell
    cell1.InitalizeAsComponent("v1_" & treeRowId & "g1", gm1, "gm1", 4,19, "")
    l.cells.Add(cell1)
    Return l
End Sub

' clicked on the navigation bar
Sub Page_NavigationbarClicked(Action As String, Value As String)
    page.SaveNavigationBarPosition
    If Action = "ABMTreeTable" Then Return
    If Action = "Contact" Then 
        myToastId = myToastId + 1 
        page.ShowToast("toast" & myToastId, "toastred", "Hello to you too!", 5000)
        Return
    End If
    If Action = "LogOff" Then
        ABMShared.LogOff(page)
        Return
    End If
    ABMShared.NavigateToPage(ws, Value)
End Sub

Sub Page_FileUploaded(FileName As String, success As Boolean) 
 
End Sub

Sub Page_ToastClicked(ToastId As String, Action As String)
     
End Sub

Sub Page_ToastDismissed(ToastId As String) 
 
End Sub

Sub Page_Ready()
    Log("ready!")
    page.RestoreNavigationBarPosition 
End Sub

' event raised when node is expanded by the user so maybe som children (like a ABMGoogleMap) needs to be refreshed
Sub View1_NeedsRefreshChildren(rowId As String)
    Dim view1 As ABMTreeTable = page.Component("view1")
    view1.RefreshChildren(rowId)
End Sub

' the user clicked on a row
Sub view1_Clicked(TreeRowId As String, TreeCellId As String)
 
    ' did he click on a plus, let him add the values of the new row
    If PlusParents.ContainsKey(TreeRowId) Then
        Dim ps As PlusStructure = PlusParents.Get(TreeRowId)
     
        ' here you can show a modal sheet where the user adds new data in the browser
        ' after saving the modal sheet, add the new data in the database and add a new leaf
     
        Log("The user added a new row: " & ps.Prefix & "_" & ps.NextValue)
     
        Dim view1 As ABMTreeTable = page.Component("view1")
        DateTime.timeFormat = "HH:mm:ss"
        view1.AddRow(BuildLev4Body1(4, ps.Prefix & "_" & ps.NextValue, ps.Prefix, False, "NEW", DateTime.time(DateTime.Now), "01:00"))
        ps.NextValue = ps.NextValue + 1
        PlusParents.Put(TreeRowId, ps)
        view1.Refresh 
    End If
 
End Sub

Sub view1_Dropped(TreeRowId As String, OnTreeRowID As String)
    Log("Dropped TreeRowId: " & TreeRowId & " on " & OnTreeRowID)
    ' make sure our in memory tree is in sync with the browser
    Dim view1 As ABMTreeTable = page.Component("view1")
    view1.UpdateRowParent(TreeRowId, OnTreeRowID)
End Sub

' here a sheet can be shown to let the user modify the row
Sub btnEdit_Clicked(Target As String)
    ' get the current row form the Target btnEdit1_1_1_1 -> 1_1_1_1
    Dim currRow As String = ABMShared.Mid2(Target, 8)
    Log("btnEdit: " & currRow)
 
    ' do your stuff in the database
 
    ' change it in the tree
    Dim view1 As ABMTreeTable = page.Component("view1")
    DateTime.timeFormat = "HH:mm:ss"
    view1.SetString(currRow, 2, DateTime.time(DateTime.Now))
    ' refresh, only what is changed will be refreshed
    view1.Refresh
End Sub

Sub btnDelete_Clicked(Target As String)
    ' get the current row form the Target btnDelete1_1_1_1 -> 1_1_1_1
    Dim currRow As String = ABMShared.Mid2(Target, 10)
    Log("btnDelete: " & currRow)
 
    ' do your stuff in the database
 
 
    ' delete it in the tree
    Dim view1 As ABMTreeTable = page.Component("view1")
    view1.RemoveRow(currRow)
    ' refresh, only what is changed will be refreshed
    view1.Refresh
End Sub
 
Last edited:
Upvote 0
Top