Creating a custom control from scratch

Just starting out? Need help? Post your questions and find answers here.
DARKGirl
User
User
Posts: 61
Joined: Wed Nov 10, 2004 10:32 pm

Creating a custom control from scratch

Post by DARKGirl »

Ok the time has come for me to attempt to make a custom control because I really need a specialised grid-gadget. I have looked at all the examples of grid gadgets in the forums, and they aren't what I need.

My goal is to make it look and function similar to this.

Image

As you notice, it looks a lot like the grid controls that are already available. I also want to create simple PB functions for this control to access the data within it.

I know this seems like a huge task, and some may even be scared away by it, but I feel that I understand API enough to give it a try.

The only problem is this: (and this is where you come in) I don't understand the concept of creating a custom control. That may make you think that I don't know API at all, but I have a set of questions below that would make you understand what I mean:

1: What do I have to do to set up a Window Class so my control will receive messages?

2: What are the list of structures that I would need in order to make my control work properly? I am quite sure it would have to remain consistent with other existing controls.

3: How would I set this control up in Pure Basic? Would everything have to be done in a Window Callback, or can I use procedures as well?

4: Would I be able to use the standard Pure Basic drawing functions such as Line, Plot, Circle, box, etc, or do I have to use all API functions?

5: To make my job easier, do you think I should subclass other controls in the new control? If, so, can you provide an example on how this is accomplished?

6: When I want to test the control, what would I have to do to get PB to display it? Would I have to use LoadLibrary?

These are all the questions I have for now. I am sure I would have more questions in the future. I am not asking anyone to provide a lot of code for me. I just need to know the foundation on how a control is set up, and then I can get to work.

oh, by the way, I don't want to use a listview gadget because I think that is somewhat tacky. Thats forcing a control to do something it wasn't really designed to do. I want to make something that is designed to do just that. I think it would be called a PropertyView Control.

Thanks in advance!
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8452
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

localmotion34 has some good examples to draw ideas from if you do a search. He always includes full source afaik.
BERESHEIT
localmotion34
Enthusiast
Enthusiast
Posts: 665
Joined: Fri Sep 12, 2003 10:40 pm
Location: Tallahassee, Florida

Post by localmotion34 »

netmaestro wrote:localmotion34 has some good examples to draw ideas from if you do a search. He always includes full source afaik.
Why thank you. (blush)

DarkGirl:

1) learn how to subclass, subclass, subclass. Look at my ownerdraw examples, specifically Listboxes, and definitely my LED meter control, and my multitrackbar. look at any of my control examples.

2) learn how to double subclass. look at my example on that

3) PLAN PLAN PLAN. Have a SPECIFIC idea of how you want this control to operate. use CUSTOM messages, and definitely use window user data, and window props (SetProp_()) ect.

4) DRAW TO A BACKBUFFER, and then bit block transfer the WHOLE control DC at once. this eliminates flickering. a great way to operate is to use a static control, and just update the static image when you are done drawing. then you never have to process #wm_paint commands.

5) look at my Listbox grid example, or Tstring Calendar clone if you want to use a "grid". but i highly recommend starting from scratch. my multitrackbar example is just that.

good luck, and post here if you need help.

EDIT:

here is an updated version of my listbox grid. this should give you and idea of what you will need to do, as in drawing the selection rectangle ect. keep in mind that you will have to keep track of each individual cell's rectangle and coordinates, which item is selected; if you are going to make a custom control.

Code: Select all

Global OriginProc,editcontrol.l ,editprocedure.l ,crtitem.l


Procedure GetPanelDisplayWindowID(hwnd,item)
  tc_item.TC_ITEM
  tc_item\mask=#TCIF_PARAM
  SendMessage_(hwnd,#TCM_GETITEM,item,tc_item)
  ProcedureReturn tc_item\lParam
EndProcedure
Procedure EditProc(hwnd,msg,wParam,lParam)
  Select msg
    Case #WM_RBUTTONDOWN
      
      ProcedureReturn 0
  EndSelect
  ProcedureReturn CallWindowProc_(editprocedure,hwnd,msg,wParam,lParam)
EndProcedure

Procedure ListboxProc( hwnd, msg,wParam,lParam)
  Select msg
    Case #WM_DRAWITEM
      hbrushSelectedFocus.l = CreateSolidBrush_(RGB(0, 0, 80))
      *textbuffer.s=Space(255)
      listb=GetWindow_(hwnd,#GW_CHILD)
      *lpdis.DRAWITEMSTRUCT=lParam
      *lptris.DRAWITEMSTRUCT=*lpdis.DRAWITEMSTRUCT
      Select *lpdis\CtlType
        Case #ODT_LISTBOX
          itemHeight=SendMessage_(*lpdis\hwndItem,#LB_GETITEMHEIGHT,0,0)
          Select *lpdis\itemState
            Case #ODS_SELECTED
              dtFlags = #DT_LEFT | #DT_VCENTER
              currentBrush = CreateSolidBrush_(RGB(0, 0, 80))
              currentTextColor = #White
              drawfoc=#False
              drawbox=#False
            Case #ODS_SELECTED | #ODS_FOCUS
              dtFlags = #DT_LEFT | #DT_VCENTER
              currentBrush = hbrushSelectedFocus
              currentTextColor = #White
              drawfoc=#True
            Case 0
              dtFlags = #DT_LEFT | #DT_VCENTER
              currentBrush = #White
              currentTextColor = RGB(0, 0, 0)
              drawfoc=#False
              drawbox=#True
          EndSelect
          SendMessage_(*lpdis\hwndItem,#LB_GETTEXT,*lpdis\itemID,*textbuffer)
          lbText$=*textbuffer
          FillRect_(*lpdis\hdc, *lpdis\rcItem, currentBrush)
          If drawfoc=#True
            DrawFocusRect_(*lpdis\hdc, *lpdis\rcItem)
          EndIf
          If drawbox=#True
            hpen=createpen_(#PS_INSIDEFRAME      ,0,#Black)
            rectangle_(*lpdis\hdc,*lpdis\rcItem\left,*lpdis\rcItem\top,*lpdis\rcItem\right,*lpdis\rcItem\bottom)
          EndIf
          SetBkMode_(*lpdis\hdc, #TRANSPARENT)
          SetTextColor_(*lpdis\hdc, currentTextColor)
          *lpdis\rcItem\left+itemHeight
          DrawText_(*lpdis\hdc, lbText$, Len(lbText$), *lpdis\rcItem, dtFlags)
          ProcedureReturn 0
      EndSelect
    Case #WM_COMMAND
      Select (wParam>>16)&$FFFF
        Case #LBN_DBLCLK
          *textbuffer1.s=Space(256)
          curitem=SendMessage_(lParam,#LB_GETCURSEL,0,0)
          crtitem=curitem
          SendMessage_(lParam,#LB_GETITEMRECT,curitem,itmrect.RECT)
          SendMessage_(lParam,#LB_GETTEXT,curitem,*textbuffer1)
          itmhght=SendMessage_(lParam,#LB_GETITEMHEIGHT,0,0)
          UseGadgetList(lParam)
          editcontrol=StringGadget(#PB_Any,itmrect\left+1,itmrect\top,itmrect\right-itmrect\left-2,itmhght,*textbuffer1,#PB_String_BorderLess)
          SetFocus_(GadgetID(editcontrol))
          editprocedure.l=SetWindowLong_(GadgetID(editcontrol),#GWL_WNDPROC,@EditProc())
          ProcedureReturn 0
        Case #LBN_SELCHANGE   
          If editcontrol
            *textbuffer2.s=Space(256)
            SendMessage_(lParam,#LB_GETTEXT,crtitem,*textbuffer2)
            SendMessage_(lParam,#LB_DELETESTRING,crtitem,0)
            SendMessage_(lParam,#LB_INSERTSTRING,crtitem,GetGadgetText(editcontrol))
            FreeGadget(editcontrol)
            editcontrol = 0
          EndIf
          ProcedureReturn 0
      EndSelect
  EndSelect
  ProcedureReturn CallWindowProc_(OriginProc,hwnd,msg,wParam,lParam)
EndProcedure   

ProcedureDLL LBGrid(x,y,width,height,itemHeight,type,parent,tabitem)
  *class.s=Space(255)
  cs=GetClassName_(parent,*class,Len(*class))
  If *class = "SysTabControl32"
    finalparent=GetPanelDisplayWindowID(parent,tabitem)
  Else
    finalparent=parent
  EndIf
  window=OpenWindow(#PB_Any,x,y,width,height,"",#PB_Window_BorderLess|#PB_Window_Invisible)
  SetWindowLong_(WindowID(window),#GWL_STYLE, #WS_CHILD|#WS_DLGFRAME|#WS_EX_CLIENTEDGE|#WS_CLIPCHILDREN|#WS_CLIPSIBLINGS )
  SetParent_(WindowID(window),finalparent)
  ShowWindow_(WindowID(window),#SW_SHOW)
  CreateGadgetList(WindowID(window))
  lb=ListViewGadget(#PB_Any,0,0,width,height,#LBS_OWNERDRAWFIXED|#LBS_HASSTRINGS|#LBS_MULTICOLUMN|#LBS_NOTIFY)
  OriginProc= SetWindowLong_(WindowID(window), #GWL_WNDPROC, @ListboxProc())
  SendMessage_(GadgetID(lb), #LB_SETITEMHEIGHT, 0, itemHeight)
  SendMessage_(GadgetID(lb), #LB_SETCOLUMNWIDTH, 80,0)
  UseGadgetList(finalparent)
  ProcedureReturn lb
EndProcedure

#WindowWidth  = 390
#WindowHeight = 350
If OpenWindow(0, 100, 200, #WindowWidth, #WindowHeight,"", #PB_Window_MinimizeGadget)
  If CreateGadgetList(WindowID(0))
    grid=LBGrid(30,20,250,200,0,0,WindowID(0),0)
  EndIf
  For a=0 To 35
    AddGadgetItem(grid,-1,Str(a))
  Next
  
  ;- create some images 
  
  ;- add some items 
  
  
  ;- event loop
  Repeat
    
    EventID = WaitWindowEvent()
    
    If EventID = #PB_Event_Gadget
      
      Select EventGadget()
        
        
        
      EndSelect
      
    EndIf
    
  Until EventID = #PB_Event_CloseWindow
  
EndIf
 
End 

Code: Select all

!.WHILE status != dwPassedOut
! Invoke AllocateDrink, dwBeerAmount
!MOV Mug, Beer
!Invoke Drink, Mug, dwBeerAmount
!.endw
DARKGirl
User
User
Posts: 61
Joined: Wed Nov 10, 2004 10:32 pm

Post by DARKGirl »

Localmotion34: Thank you for your post and your help, but thats what I was trying to avoid; wading through other people's code that I can't even begin to understand. I tried reading the code that you posted, and I don't know whats happening. The arrays, structures, and variables are way too obscure for me. What I am looking for are lists of items, such as structures, and their function. And you tell me that I need to learn how to subclass but you don't provide an example :-(

EDIT: OK, thanks to MSDN I found out the function that registers the window class http://msdn2.microsoft.com/en-us/library/ms633574.aspx

That link now gives me the basic information I need to continue, but now I have more questions.

The document says that the first thing I need to do is 'fill in a WNDCLASSEX structure with the window class information'. How is this done?

Do I just create a regular array with the structure and just fill it with information?

Thanks again for the help.
DARKGirl
User
User
Posts: 61
Joined: Wed Nov 10, 2004 10:32 pm

Post by DARKGirl »

I just received some new information from Rings. He says that that control that is in the image example is part of the .net interface. So I ask him if it is possible to use .net controls in PB, he says it is possible. Then, Dige tells me that f34ak created a framework to init com objects in PB. So I am ready to give it my all, except I don't know the proper name of that control in the image. Does anyone have any ideas on which one it is?

But, I have a nagging feeling that this simply isn't going to work and I will have to continue creating my own control. :?
milan1612
Addict
Addict
Posts: 894
Joined: Thu Apr 05, 2007 12:15 am
Location: Nuremberg, Germany
Contact:

Post by milan1612 »

It is named PropertyGrid and is part of the .NET Namespace System.Windows.Forms
BTW I don't think you can use .NET Classes (and an .NET Control is a .NET Class)
in PB, but I'm not sure

EDIT: The PropertyGrid isn't a normal grid. It displays the properties of
another associated .NET Class, so you can't use it in PB.
Last edited by milan1612 on Tue Jun 12, 2007 12:08 pm, edited 2 times in total.
Windows 7 & PureBasic 4.4
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

Do you really want to ship an application written in Purebasic that requires the .Net framework to be installed? :wink: Of course all OS's from XP onward have this installed by default anyhow, but it's still a hell of a pre-requisite!

If I were you I'd persevere with your custom control.

As it turns out I am working (in my spare time) on just such a control at the moment, but do not bother registering a custom window class. Just grab hold of a listicon gadget and rip it apart with subclassing + custom draw etc. You don't even require an ownerdrawn listicon, unless you prefer to work this way (it's a matter of personal preference really).


**EDIT: in order to use a .net class / control within a PB app you will need to dip into a .net language capable of producing a COM class; wrap the control within such an object and then use PB interfaces etc. to grab a hold of an instance of the COM object. Not an easy thing to do by a long shot.
I may look like a mule, but I'm not a complete ass.
Trond
Always Here
Always Here
Posts: 7446
Joined: Mon Sep 22, 2003 6:45 pm
Location: Norway

Post by Trond »

Delphi has a TValueListEditor that works quite similar to that control. It should be much easier to use than to use a .Net control. And it doesn't require .Net.
DARKGirl
User
User
Posts: 61
Joined: Wed Nov 10, 2004 10:32 pm

Post by DARKGirl »

Srod: Yes, I really don't want to use anything from the .NET library, so I trashed that idea about three hours ago. I do not want to use any common controls as a foundation as I think thats a bit tacky like I said before. I rather start from scratch. I was reading on MSDN and it doesn't really go into depth on creating your own control. I did manage to peck out some code that does absolutely nothing at this point, and I am quite sure it is full of bugs. :oops: Here it is:

Code: Select all

Procedure MyWindowCallback(WindowID, message, wParam, lParam)
  Select message
   
  EndSelect
  
  Result = #PB_ProcessPureBasicEvents
  ProcedureReturn Result
EndProcedure

PV.WNDCLASSEX ;PV = PropertyViewer
PVClass.s = "PViewer"


PV\cbSize = SizeOf(PV)
PV\style = #CS_HREDRAW | #CS_VREDRAW
PV\lpfnWndProc = @MyWindowCallback()
PV\cbClsExtra = 0
PV\cbWndExtra = 0
PV\hInstance = hInstance 
PV\hIcon = LoadIcon_(NULL, IDI_APPLICATION)
PV\hCursor = LoadCursor_(NULL, IDC_ARROW)
PV\hbrBackground = GetStockObject_( #WHITE_BRUSH)
PV\lpszMenuName = 0
PV\lpszClassName = @PVClass
PV\hIconSm = 0

registerclassex_(@PV)
hwnd = CreateWindow_("MainWClass", "sample", #WS_OVERLAPPEDWINDOW, #CW_USEDEFAULT, #CW_USEDEFAULT, #CW_USEDEFAULT, #CW_USEDEFAULT, #Null, #Null, hInstance, #Null)
ShowWindow_(hwnd, nCmdShow)
UpdateWindow_(hwnd)

Debug hwnd 

Repeat
  Event = WindowEvent()
  
  Delay(1)
ForEver
I am almost certian that thats not the way you create new windows. I read that since controls are also windows, apparently the way to create one is the same way to create a 'real' window. Any help from anyone would be appreciated. :)
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

I wouldn't say that basing a custom control on one of Window's common controls is 'tacky' as such; quite the opposite. The fact is that the control you seek will inevitably reproduce a lot of the functionality of a Window's list view control anyhow, so why reinvent the wheel? Why not use a Window's listview control's built in customisation? Indeed the property box control I'm working on (based on a listview) looks pretty good if I say so myself! :) Nice gradient colouring etc!

Still, it's up to you. If a control based on a custom window's class is what you want, then fair enough. It will be a major undertaking, however, to produce the control you are after. I suggest looking at ts-soft's PB gadget library and at least utilise some of the workings therein else you will not easily be able to use PB's natural event handling mechanisms.

Some of the steps you face are very complex; e.g. scrolling! This could be a flaming nightmare with variable column widths etc. If drawing to an off-screen buffer then you'll need to ensure, for performance reasons, that you only redraw the visible portions before blitting to the screen. This is the best way of ensuring a smooth scroll. If not drawing to a buffer first then... good luck! :) Then there are the child controls which you will need to use for editing cells etc. Each will need to be subclassed and really, in the case of direct editing at least, you'll need to use a Window's edit control which will require some 'bullying' in order for it to behave properly in conjunction with, for example, scrolling the main control. You scroll the main control and the various edit controls will stay put and completely trash the painting etc. Herein lies a massive source of flicker!

Anyhow, I'm not trying to put you off at all, I just reckon you should not dismiss basing such a control on a listview so quickly.

As for the code you've posted - you cannot use #PB_ProcessPureBasicEvents for a window in you've resgistered the class yourself. You need to use a more standard Window procedure and you'll be better off using the API for message retrieval; i.e. GetMessage_(), TranslateMessage_() etc. Remember that it is not the control's responsibility to fetch messages from the message queue, this is the job of the main application so you'll want to separate the code for the control from any event loops etc. I'm not at my development machine at the moment and so I cannot post an example. You should be able to find some working code in these forums though.

Whatever you decide, then I wish you luck with this. It's certainly an interesting thing to tackle, the kind of thing which I like working on myself. :)
Last edited by srod on Tue Jun 12, 2007 2:36 pm, edited 1 time in total.
I may look like a mule, but I'm not a complete ass.
DARKGirl
User
User
Posts: 61
Joined: Wed Nov 10, 2004 10:32 pm

Post by DARKGirl »

Haha, I am going to subclass items on my custom control when need be. I just don't want to subclass anything on an existing control, because my control is going to have some special functionality that I couldn't implement on a listicongadget anyway.
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

DARKGirl wrote:... because my control is going to have some special functionality that I couldn't implement on a listicongadget anyway.
Now that does probably mean that a custom window's class is required! :)

Still, through my work on egrid etc. about the only thing I couldn't squeeze out of a listicon was rows with different heights. It's just not possible.

Another option, as I think someone mentioned (and a damn good compromise :wink: ) is to base your control on a static control, even a Purebasic container. Slap a few scrollbars on it and you're off! There really is nothing you can't do with a static control and, at least with a PB container, you can utilise a lot of PB commands such as ResizeGadget() etc. Also of course users of your control can use PB commands on your control.
I may look like a mule, but I'm not a complete ass.
DARKGirl
User
User
Posts: 61
Joined: Wed Nov 10, 2004 10:32 pm

Post by DARKGirl »

actually thats not a bad idea since a container doesnt really do anything except group other controls together...hmm

I still don't know where to begin though. My attempts of registering a window class have been moot


edit: grr Srod come in the PB IRC room so I can chat with you :-P
srod
PureBasic Expert
PureBasic Expert
Posts: 10589
Joined: Wed Oct 29, 2003 4:35 pm
Location: Beyond the pale...

Post by srod »

DARKGirl wrote:grr Srod come in the PB IRC room so I can chat with you :-P
Having just moved house I'm on a dial-up for at least another 5 weeks whilst BT get their arses in gear and put me in another line. Only then can I get broadband and think about IRC etc. Until then I can only log on very infrequently! :cry:
I may look like a mule, but I'm not a complete ass.
DARKGirl
User
User
Posts: 61
Joined: Wed Nov 10, 2004 10:32 pm

Post by DARKGirl »

Oh I see. But IRC works with dialup!


Does anyone else have any help they could provide? Look at my example, is that done right?
Post Reply