Friday, August 12, 2005

A number of other languages have a splitter control of some sort, why not Visual FoxPro? To be fair, I have seen some splitter classes floating around from time to time, but I thought I'd try my hand at it. I was convinced it couldn't be too difficult, and Visual FoxPro didn't disappoint me. Below is a download link for the class library that I placed my splitter class in (based on shape) and an example form showing how the class is used (I've taken a screen shot of it so you can see what it looks like). Now, if you want to try the splitter class out before you go to the trouble of downloading it, I've also created a cut-n-paste/execute example that you'll find directly beneath the screen shot. This splitter control is designed to work with Visual FoxPro 9.0. If you are using a previous version then you will need to modify/delete a few things.

Download Splitter.vcx and Form Example (8 KB approx.)

*!* Cut-N-Paste the code below into a prg file and execute it to see and try out a working example of the splitter class

PUBLIC oform1

oform1=NEWOBJECT("form1")
oform1.Show
RETURN

************************************
DEFINE CLASS form1 AS form
************************************

 DoCreate = .T.
 Caption = "Splitter Example"
 Name = "Form1"
 MDIForm = .T.
 Autocenter = .T.
 
 ADD OBJECT text1 AS textbox WITH ;
  Anchor = 11, ;
  Height = 29, ;
  Left = 0, ;
  Top = 0, ;
  Width = 375, ;
  Name = "Text1"

 ADD OBJECT edit1 AS editbox WITH ;
  Anchor = 7, ;
  Height = 217, ;
  Left = 0, ;
  Top = 33, ;
  Width = 184, ;
  Name = "Edit1"

 ADD OBJECT edit2 AS editbox WITH ;
  Anchor = 15, ;
  Height = 217, ;
  Left = 188, ;
  Top = 33, ;
  Width = 187, ;
  Name = "Edit2"

 ADD OBJECT splitter1 AS splitter WITH ;
  Top = 32, ;
  Left = 183, ;
  Height = 219, ;
  Width = 6, ;
  Anchor = 7, ;
  Name = "Splitter1"

 ADD OBJECT splitter2 AS splitter WITH ;
  Top = 28, ;
  Left = 0, ;
  Height = 6, ;
  Width = 376, ;
  Anchor = 10, ;
  vertical = .F., ;
  minimumsize = 29, ;
  Name = "Splitter2"

 PROCEDURE Init
  This.text1.Value = "Visual FoxPro Rocks!"
  This.Edit1.Value = "Visual FoxPro is an extremely versatile development tool. " + ;
    "Not only does it allow a developer to create great datacentric applications, " + ;
    "it also allows the developer to extend the actual language with new classes, " + ;
    "code libraries, and hooks. If you're an avid Visual FoxPro developer like myself " + ;
    "then good for you. And, if you're not, then I encourage you to give Visual FoxPro " + ;
    "a try. You won't regret it"
  This.Edit2.Value = "This is an example of a splitter class for use in Visual FoxPro 9.0 forms. " + ;
    "It is pure Visual FoxPro, so there aren't any additional ActiveX or DLL dependencies " + ;
    "to worry about when distributing your application. To see it in action move the " + ;
    "horizontal and vertical splitters around on this form. Also, by resizing the form you " + ;
    "can see that it handles the new Visual FoxPro 9.0 Anchors property with aplomb."
 ENDPROC

ENDDEFINE

************************************
DEFINE CLASS splitter AS shape
************************************
 Height = 182
 Width = 8
 MousePointer = 9
 SpecialEffect = 0
 Style = 0
 mousedownat = 0 && Tracks mouse and allows class to ignore moves caused by resizing form
 vertical = .T. && Set to .F. for horizontal splitter
 minimumsize = 40 && This is how small (in pixels) the panels can get when moving the splitter
 Name = "splitter"

 PROCEDURE MouseLeave
  LPARAMETERS nButton, nShift, nXCoord, nYCoord
  This.mousedownat = 0
 ENDPROC

 PROCEDURE Move
  LPARAMETERS nLeft, nTop, nWidth, nHeight
  *!* If you want to move the splitter during runtime and have it move the other controls
  *!* then set mousedownat != 0 and call this move method of the splitter
  *!* remember to set mousedownat back to 0 when you are done moving the splitter

  LOCAL loControl, llLockScreenWas, lnMovement, llIsSplitter, lcUniqueTag, lnMarginOfError, lnAnchorWas
  IF this.MouseDownAt == 0
   DODEFAULT(m.nLeft, m.nTop, m.nWidth, m.nHeight)
   RETURN
  ENDIF

  m.loControl = NULL

  *!* The following tag can be placed in controls you don't want moved as well
  m.lcUniqueTag = "DoN't_MoVe_SpLiT" && Just something that is pretty well guaranteed to be unique
  THIS.TAG = m.lcUniqueTag
  m.llLockScreenWas = THISFORM.LOCKSCREEN && JIC the screen was already locked
  THISFORM.LOCKSCREEN = .T.

  m.lnMovementLeft =  m.nLeft - THIS.LEFT
  m.lnMovementTop =  m.nTop - THIS.Top

  FOR EACH m.loControl IN THIS.PARENT.CONTROLS
   IF m.loControl.TAG = lcUniqueTag && this splitter so just loop
    LOOP
   ENDIF
   IF PEMSTATUS(m.loControl,"Anchor",5)
    m.lnAnchorWas = m.loControl.Anchor
    m.loControl.Anchor = 0
    m.llIsSplitter = m.loControl.CLASS = "Splitter"
    IF THIS.vertical && Vertical Splitter
     lnMarginOfError = INT(This.width/2) && JIC the developer got the splitter a little too close
     IF m.loControl.LEFT <= THIS.LEFT && Control is to the left of splitter
      IF (m.loControl.LEFT + m.loControl.WIDTH) <= (THIS.LEFT + lnMarginOfError) AND !m.llIsSplitter
       m.loControl.WIDTH = MAX(m.loControl.WIDTH + m.lnMovementLeft, 0)
      ENDIF
     ELSE  && Control is to the right of splitter
      IF !m.llIsSplitter
       m.loControl.WIDTH = MAX(m.loControl.WIDTH - m.lnMovementLeft, 0)
      ENDIF
      m.loControl.LEFT = m.loControl.LEFT + m.lnMovementLeft
     ENDIF
    ELSE && Horizontal Splitter
     lnMarginOfError = INT(This.Top/2) && JIC the developer got the splitter a little too close
     IF m.loControl.TOP <= THIS.TOP && Control is above the splitter
      IF (m.loControl.TOP + m.loControl.HEIGHT) <= (THIS.TOP + lnMarginOfError) AND !m.llIsSplitter
       m.loControl.HEIGHT = MAX(m.loControl.HEIGHT + m.lnMovementTop, 0)
      ENDIF
     ELSE  && Control is below the splitter
      IF !m.llIsSplitter
       m.loControl.HEIGHT = MAX(m.loControl.HEIGHT - m.lnMovementTop, 0)
      ENDIF
      m.loControl.TOP = m.loControl.TOP + m.lnMovementTop
     ENDIF
    ENDIF
    m.loControl.Anchor = m.lnAnchorWas
   ENDIF
  NEXT
  m.lnAnchorWas = This.Anchor
  This.Anchor = 0
  DODEFAULT(m.nLeft, m.nTop, m.nWidth, m.nHeight) && Finally move the splitter
  This.Anchor = m.lnAnchorWas
  THISFORM.LOCKSCREEN = m.llLockScreenWas
  THIS.TAG = ""
 ENDPROC

 PROCEDURE MouseMove
  LPARAMETERS nButton, nShift, nXCoord, nYCoord
  LOCAL lnMovement
  IF m.nButton = 1 AND !(this.mousedownat == 0)
   IF THIS.vertical
    IF m.nXCoord != THIS.mousedownat
     m.lnMovement = m.nXCoord - THIS.mousedownat
     IF BETWEEN(THIS.LEFT + m.lnMovement, This.minimumsize, THIS.PARENT.WIDTH - THIS.WIDTH - This.minimumsize)
      THIS.MOVE(THIS.LEFT + m.lnMovement, THIS.TOP, THIS.WIDTH, THIS.HEIGHT)
      THIS.mousedownat = m.nXCoord
     ENDIF
    ENDIF
   ELSE && Horizontal
    IF m.nYCoord != THIS.mousedownat
     m.lnMovement = m.nYCoord - THIS.mousedownat
     IF BETWEEN(THIS.TOP + m.lnMovement, This.minimumsize, THIS.PARENT.HEIGHT - THIS.HEIGHT - This.minimumsize)
      THIS.MOVE(THIS.LEFT, THIS.TOP + m.lnMovement, THIS.WIDTH, THIS.HEIGHT)
      THIS.mousedownat = m.nYCoord
     ENDIF
    ENDIF
   ENDIF
  ENDIF
 ENDPROC

 PROCEDURE MouseDown
  LPARAMETERS nButton, nShift, nXCoord, nYCoord
  IF THIS.vertical
   THIS.mousedownat = nXCoord
  ELSE
   THIS.mousedownat = nYCoord
  ENDIF
 ENDPROC

 PROCEDURE Init
  IF !THIS.vertical
   THIS.MOUSEPOINTER = 7 && NS
  ENDIF
 ENDPROC

 PROCEDURE MouseUp
  LPARAMETERS nButton, nShift, nXCoord, nYCoord
  This.mousedownat = 0
 ENDPROC
 
ENDDEFINE

Saturday, August 13, 2005 2:32:53 AM (Central Daylight Time, UTC-05:00)  #    Comments [5]
Tuesday, September 13, 2005 2:47:39 PM (Central Daylight Time, UTC-05:00)
Hi Craig

Needed a splitter class so downloaded your class. The sample seemed to look good, except for some jerky behaviour.

Unfortunately it just does not work "as advertised".

Even the sample form that comes with the download does not work properly.

To test this I just did the following:
Changed the BackColor of the splitters to Red for the top (Horizantal) and blue for the vertical.

Then I resized the form a bit larger and moved all the objects so that they were centered leaving space from the edge of the form all round. This is so that the odd effect can be seen.

Now run the form and move the blue bar(Vertical) left and right. So far so good. Now Try moving the red bar(Horizantal) down. See the blue horizantal splitter moving down as well?

It gets even worse if you add more splitters. Copy and paste the Topmost Textbox and the red(Horiz) splitter. Move the copied textbox to the bottom of the form and move the copied splitter just above it so that the splitters now look like a H lying down. So we have, from top, Text1,Splitter1(horiz) then edit1,splitter2(vert) and edit3 just below and from left to right. Next is splitter3(horiz) and finally below that text2. Change the anchor property of the bottom most splitter and textbox as needed.

Now run the form and the blue(vert) splitter behaves. Now try moving either of the Horiz bars and see the effect.

It gets worse when I removed all anchor properties of all objects. Any thoughts about this?
Saturday, October 01, 2005 8:52:34 AM (Central Daylight Time, UTC-05:00)
Bernard Bout,

I'll be back to you on this just as soon as I've had an opportunity to try out your examples. If you want to speed the process along you could send me the forms that reproduce the problems (via email) and I will take a quick look and see if a quick fix is there for the taking. Otherwise, I am short on time these days, at least until Southwest Fox is over, but I will address these issues as soon as possible directly after the conference.
Monday, January 02, 2006 5:13:30 PM (Central Standard Time, UTC-06:00)
Another brilliant piece of programming from Craig. My only regret is that I didn't have this a couple of years ago when I was struggling to create my own splitter. I succeeded in doing that, but it was never as slick as this one.

By the way, I didn't see any of the problems with this class that Bernard reported. But, then again, I haven't tested it as thoroughly as he did. Craig, did you ever manage to resolve Bernard's issues?

Mike
Wednesday, January 04, 2006 12:04:59 PM (Central Standard Time, UTC-06:00)
Hi Mike,

Thank you for the kind words. I don't believe I ever received the bug repro form from Bernard via email. I haven't seen any problems with the splitter, including the jerky behavior that he describes. I am certain that there are probably ways that the splitters can be placed on a form that they will mess up, however I just haven't run into it using stuff similar to what I showed in this blog entry. If anyone sends me some a repro of the bugs that Bernard details, I would certainly look into it, but I guess I just don't have the time to try and repro this myself.
Wednesday, September 24, 2008 3:47:17 PM (Central Daylight Time, UTC-05:00)
Hello, I'm trying to use your work.
I'm not an experienced programmer so probably I'll do a lot of error.
I had some problem using it and I discovered that for a reason I don't understand
in the mouse move event there is code that ask THIS.PARENT.CONTROLS and try to do
something with every control : ok, I can (and in fact I did) write DoN't_MoVe_SpLiT
in the tag property of every other control of the form to evitate this, but this force me
to write something in the tag property that probably I can't use for any other stuff.

My newbie question is : how can I place a container with some control and your splitter in it and force the splitter to exclude every control out of the container without touch the tag property ?

Thank's in advance
Bert

P.S.
Sorry for my bad english
Bert
Name
E-mail
(will show your gravatar icon)
Home page

Comment (Some html is allowed: a@href@title, b, blockquote@cite, em, i, strike, strong, sub, super, u)  

Enter the code shown (prevents robots):


 

Archive

<October 2008>
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678