Menu

Documentation

Leo

Discussion

  • Leo

    Leo - 2012-11-20

    Welcome to XUI Development Document!

    What's Direct UI ?

    Traditionally, Windows UI are built upon the native window hierarchy, and thus restricted in several areas. While you can certainly generate a nice application quickly with the built-in window based control-set and get a standard clean look, you will soon enough stumble upon the limitations of the window based controls - especially if you want to build something that looks a little more flashy. The native Win32 window based drawing technique is limited, next to no support for transparent windows(layered windows support transparency well, but it doesn't support parent-child hierarchy).

    Direct UI is a way of building UI in which we use a native Win32 window as a container only and all controls in this container are drawn directly on the container window. Thus, controls gain abilitity to become transparent. At the same time, getting of the complexity of native Win32 window management, controls are easier to make and are much more flexible. A well-implemented Direct UI framework can provide much better performance, comparing to the traditional window based UI building method when building complex and flashy UI of applications, thanks to its optimized control management. Further, a Direct UI implementation can provide such functions as building UI with XML, controlling the behaviors of controls with scripts, which simplify the work need to build UI.

    Document

    1. Basics, how controls are organized and drawn.
    2. Basics, memory management and remote reference.
    3. Drawing tools, IXImage and IXText.
    4. The fundamental frame, XFrame.
    5. Building UI with XML.
    6. Layout controls: XDock and XScrollFrame. (coming soon...)
    7. Static and Edit: XStatic and XEdit. (coming soon...)
    8. Buttons: XButton and XMultifaceButton. (coming soon...)
    9. List and Tree: Building list using aggregation and XTree (coming soon...)
    10. Animation: XAnimation. (coming soon...)
    11. Building custom controls. (coming soon...)
    12. Native control integration: XSysControl. (coming soon...)
     

    Last edit: Leo 2012-11-30
  • Leo

    Leo - 2012-11-21

    Basics, how controls are organized and drawn.

    In summary, we use a Win32 native layered window as our host window, and all the controls appear on that window are drawn directly.

    In XUI, UI components that draw something on the screen are called frames. All controls mentioned above are frames, and host windows are also frames. These frames are completely independent UI units. They each has its own coordinate system, handles its own messages and receives its own DC when drawing. On the other hand, each frame can contain one or more child frames. A child frame is always drawn within its parent frame and has its position relative to the top-left corner of its parent frame. Usually, to really draw something on the screen, the root frame of this frame tree is a host window frame.

    When drawing, each frame draws itself first, and then its child frames one by one. We eliminated the complexity by not coming up with a z-order concept. Instead, frames drawn earlier are always on the top of frames drawn later.

    Frames are implemented as a C++ class called CXFrame, all UI components are directly or indirectly derived from this class. CXFrame implements basic behaviors of frames mentioned above, such as drawing, positioning and sizing, child frame management, etc. Specially, host windows are implemented as a C++ class called CXWindow, which is directly derived from CXFrame.

     

    Last edit: Leo 2012-11-21
  • Leo

    Leo - 2012-11-22

    Basics, memory management and remote reference.

    Memory management

    We made things easy by only using the C++ native new/new[] and delete/delete[] for memory allocation and deallocation. If you want to apply your own memory allocation/deallocation policy, just supply your operator new/operator new[] and operator delete/operator delete[] in classes you are concerned about.

    A frame may hold kinds of objects. For example, a Scroll Frame may hold an IXDraw object for drawing its background, two child frames for its vertical and horizon scroll bars and a child frame to contain its contents. CXFrame and classes derived from CXFrame provide methods to receive and hold such objects. Once held by a frame, the life cycle of such an object will be managed by this frame. Specially, a frame will destroy (using destroy method if there is one) and delete all the objects it holds when being destroyed (using the destroy method of CXFrame).

    Remote reference

    As mentioned above, once held, the life cycle of an object is managed by the frame that holds it. However, there are times when a held object has to be referenced(usually via a pointer) by another object while its life cycle is managed by the frame that holds it. For example, an user-defined object that is responsible for the program logic may keep a reference to a button, so that it can enable or disable the button when necessary. And at the same time, this button is held by its parent frame. When the button is destroyed and deleted by its parent frame, the reference kept by the user-defined object becomes a bad reference and is
    easy to cause a crash.

    To solve this kind of problems, it's not proper to simply use the reference count technic. Keeping a reference to an object that is already destroyed by the frame that holds it makes no sense and is easy to cause memory leaks. So we came up with a concept remote reference. A remote reference is a reference to an object that will automatically become NULL when the object it references to is deleted. So, a remote reference is always safe to use as long as it's not NULL.

    The remote reference mechanism is implemented as CRemoteRef<> template class and CRemoteRefSupport. Using a CRemoteRef<X> instance is just like using a X * native C++ pointer. A CRemoteRef<X> instance will automatically become NULL when the object it refers to is deleted. Here, X can be any class that is derived from SUPPORT_REMOTE_REFERENCE(which is virtual public CRemoteRefSupport) or any of its subclasses.

     

    Last edit: Leo 2012-11-27
  • Leo

    Leo - 2012-11-27

    Drawing tools, IXImage and IXText

    There are two basic tasks when drawing an user interface: drawing an image and drawing text. These two tasks are fulfilled by two interfaces: IXImage and IXText which are both derived from IXDraw.

    IXDraw

    ~~~~~~~~~~~~~~~~~~~~~
    :::C++
    class IXDraw
    {
    public:
    virtual BOOL Draw(HDC hDC, const CRect &rcDraw) = 0;
    virtual BOOL SetAlpha(BYTE cAlpha) = 0;
    virtual BOOL SetDstRect(const CRect &rcDst) = 0;

    public:
    virtual ~IXDraw(void){};
    };
    ~~~~~~~~~~~~~~~~~~~~~~
    An IXDraw instance can draw the image or text it holds on a DC we give to it. rcDst of SetDstRect, called destination rectangle, designates a rectangle of the DC we give to it where it will draw our image or text. rcDraw of Draw, called update rectangle, designates a rectangle of the DC we give to it which is the part that needs to be drawn. Optionally, we can specify an alpha value that will be added to our source image or text, via cAlpha of SetAlpha.

    IXImage

    class IXImage : public IXDraw
    {
    public:
        enum DrawImageType{DIT_UNKNOW, DIT_NORMAL, DIT_STRETCH, DIT_9PART, DIT_3PARTH, DIT_3PARTV, DIT_CENTER};
    
    public:
        virtual BOOL SetSrcRect(const CRect &rcSrc) = 0;
    
    public:
        virtual BOOL SetDrawType(DrawImageType dit) = 0;
        virtual BOOL SetPartRect(const CRect &rcPart) = 0;
    
    public:
        virtual INT GetImageHeight() = 0;
        virtual INT GetImageWidth() = 0;
    };
    

    IXImage is drived from IXDraw, so it is about all we metioned above. When drawing an image, there are four drawing types that determine how the image is placed on the destination rectangle of the DC.

    DIT_NORMAL : The image is directly drawn on the destination rectangle without any sizing.
    DIT_STRETCH : The image is stretched to fill the destination rectangle.
    DIT_9PART : The image is partly stretched to fill the destination rectangle. We can specify a rectangle of the image, called part rectangle, which is the part of the image to be stretched, via rcPart of SetPartRect.
    DIT_3PARTH : Same as DIT_9PART, except that the top side and bottom side of the part rectangle must be same as the top side and bottom side of the image.
    DIT_3PARTV : Same as DIT_9PART, except that the left side and right side of the part rectangle must be same as the left side and right side of the image.

    Further, it's not necessary to draw the whole image. We can specify a rectangle of the image, called source rectangle, only which part of the image is sized and drawn.

    IXText

    class IXText : public IXDraw
    {
    public:
        virtual CString GetText() = 0;
    };
    

    IXText is drived from IXDraw, so it is about all we metioned above. Additionally, we can get the text it holds via GetText.

    How to create an IXImage or IXText instance ?

    While we can implement these interfaces by ourselves, XUI provides a GDI-plus-based implementation of IXImage, CXImageGdiPlus, and a GDI-plus-based implementation of IXText, CXTextGdiPlus.

    CXImageGdiPlus and CXTextGdiPlus provide such methods as loading images from files, loading images from resources, loading text with specified font face and font size, etc.

     

    Last edit: Leo 2012-11-28
  • Leo

    Leo - 2012-11-29

    The fundamental frame, XFrame.

    All UI components including host windows are frames, called XFrame in XUI, so all UI component classes are derived from the fundamental frame class, CXFrame.

    XFrame is responsible for basic UI component tasks, such as sizing and positioning, drawing, child frame management, etc. Derived from CXFrame, our UI component classes obtain all that is necessary for an UI component. And by overriding virtual methods in CXFrame, we can custom these basic behaviors.

    Sizing and positioning. [Verison 1.x]

    [Note that this section does not apply to version 2.x. Version 2.0 add a layout system and will be documented later]

    Each frame has two sizing modes: width sizing mode and height sizing mode. These modes designate how to control the width/height of the frame.

    enum WIDTH_MODE // of the class CXFrame
    {
        WIDTH_MODE_NORMAL = 0, 
        WIDTH_MODE_REACH_PARENT, 
        WIDTH_MODE_WRAP_CONTENT,
        WIDTH_MODE_ADAPT_BACKGROUND, 
        WIDTH_MODE_NOT_CHANGE
    };
    
    enum HEIGHT_MODE // of the class CXFrame
    {
        HEIGHT_MODE_NORMAL = 0, 
        HEIGHT_MODE_REACH_PARENT, 
        HEIGHT_MODE_WRAP_CONTENT, 
        HEIGHT_MODE_ADAPT_BACKGROUND, 
        HEIGHT_MODE_NOT_CHANGE
    };
    

    WIDTH_MODE_NORMAL/HEIGHT_MODE_NORMAL: The width/height of the frame is controlled manually.
    WIDTH_MODE_REACH_PARENT/HEIGHT_MODE_REACH_PARENT: The right/bottom side of the frame always reaches the right/bottom side of its parent frame.
    WIDTH_MODE_WRAP_CONTENT/HEIGHT_MODE_WRAP_CONTENT: The width/height of the frame is always adjusted to just wrap its contents.
    WIDTH_MODE_ADAPT_BACKGROUND/HEIGHT_MODE_ADAPT_BACKGROUND: The width/height of the frame is always the same as its background.
    WIDTH_MODE_NOT_CHANGE/HEIGHT_MODE_NOT_CHANGE: Not change current width/height mode.

    virtual BOOL SetWidthHeightMode(
        WIDTH_MODE aWidthMode, 
        HEIGHT_MODE aHeightMode); 
    // of the class CXFrame
    

    Via CXFrame::SetWidthHeightMode, we can set the width/height mode of a frame.

    virtual BOOL SetRect(const CRect &rcFrame);
    // of the class CXFrame
    

    We can set the width/height and position of a frame via CXFrame::SetRect. The position of a frame is always relative to its parent frame. For a root frame, its position is relative to the screen.

    If the width mode/height mode of a frame is not WIDTH_MODE_NORMAL/HEIGHT_MODE_NORMAL, the left member and the top member of rcFrame of SetRect are taken, but the right member and the bottom member of rcFrame of SetRect are ignored. Otherwise, all the members of rcFrame of SetRect are taken.

    Drawing

    virtual BOOL PaintUI(HDC hDC, const CRect &rect);
    virtual BOOL PaintBackground(HDC hDC, const CRect &rect);
    virtual BOOL PaintForeground(HDC hDC, const CRect &rect);
    
    virtual IXImage * SetBackground(IXImage * pDrawBackground);
    
    virtual BOOL InvalidateRect(const CRect & rect);
    
    // of the class CXFrame
    

    When a frame needs to be drawn, its PaintUI will be called. hDC of PaintUI is where the drawing takes place. rect of PaintUI is the part of the frame that needs to be drawn.

    PaintUI first calls PaintBackground, which will draw the background of the frame. We can set the background of a frame via SetBackground.

    PaintUI then calls PaintForeground, which will draw all the child frames of the frame.

    Note that, because we use layered windows as host windows, each frame needs to be drawn only once until InvalidateRect is called manually by someone.

    Sizing and positioning. [Verison 2.x]

    class LayoutParam
    {
    public:
        enum SPECIAL_METRICS { METRIC_REACH_PARENT = -1,  METRIC_WRAP_CONTENT = -2};
        ...   
    };
    
    class MeasureParam
    {
    public:
        enum MEASURE_SPEC { MEASURE_UNRESTRICTED, MEASURE_ATMOST, MEASURE_EXACT };
    
    public:
        MEASURE_SPEC m_Spec;
        INT m_nNum;
    };
    
    virtual BOOL InvalidateLayout();
    
    BOOL MeasureWidth(const MeasureParam & param);
    BOOL MeasureHeight(const MeasureParam & param);
    INT GetMeasuredWidth();
    INT GetMeasuredHeight();
    
    BOOL Layout(const CRect & rcRect);
    
    virtual BOOL OnMeasureWidth(const MeasureParam & param);
    virtual BOOL OnMeasureHeight(const MeasureParam & param);
    virtual BOOL OnLayout(const CRect & rcRect);
    

    Layout is a two pass process: a measure pass and a layout pass. The measuring pass is implemented in MeasureWidth and MeasureHeight and is a top-down traversal of the view tree. Each frame pushes dimension specifications down the tree during the recursion. At the end of the measure pass, every frame has stored its measurements. The second pass happens in Layout and is also top-down. During this pass each parent is responsible for positioning all of its children using the sizes computed in the measure pass.

    When a frame's MeasureWidth and MeasureHeight method returns, its getMeasuredWidth and getMeasuredHeight values must be set, along with those for all of that frame's descendants. A frame's measured width and measured height values must respect the constraints imposed by the frame's parents. This guarantees that at the end of the measure pass, all parents accept all of their children's measurements. A parent frame may call MeasureWidth and MeasureHeight more than once on its children. For example, the parent may measure each child once with unspecified dimensions to find out how big they want to be, then call MeasureWidth and MeasureHeight on them again with actual numbers if the sum of all the children's unconstrained sizes is too big or too small.

    The measure pass uses two classes to communicate dimensions. The CXFrame::LayoutParam class is used by frames to tell their parents how they want to be measured and positioned. The CXFrame::LayoutParams class just describes the position the frame wants to sit, how big the frame wants to be for both width and height and their margins. For width and height, it can specify one of:

    An exact number.
    METRIC_REACH_PARENT, which means the frame's right edge and bottom edge want to reach those of its parent's. (minus its right margin and bottom margin respectively).
    METRIC_WRAP_CONTENT, which means that the frame wants to be just big enough to enclose its content.

    Frames may provide subclasses of LayoutParam to its child frames. Child frames MUST use the LayoutParam provided by its parent frame to specify how they want to be measured and positioned.

    MeasureParam are used to push requirements down the tree from parent to child. A MeasureParam can be in one of three modes:

    MEASURE_UNRESTRICTED: This is used by a parent to determine the desired dimension of a child view. For example, a CXDock may call MeasureHeight on its child with m_Spec set to UNSPECIFIED and call MeasureWidth with m_Spec set to MEASURE_EXACT and m_nNum set to 240 to find out how tall the child frame wants to be given a width of 240 pixels.
    MEASURE_EXACT: This is used by the parent to impose an exact size on the child. The child must use this size, and guarantee that all of its descendants will fit within this size.
    MEASURE_ATMOST: This is used by the parent to impose a maximum size on the child. The child must gurantee that it and all of its descendants will fit within this size.

    To request layout, call InvalidateLayout. This method is typically called by a frame on itself when it believes that the size or position of its child frames no longer fits the LayoutParam of them.

    Child frame management

    virtual BOOL InsertFrame(CXFrame * pFrame, UINT nIndex);
    BOOL AddFrame(CXFrame * pFrame);
    virtual CXFrame * RemoveFrame(UINT nIndex);
    BOOL RemoveFrame(CXFrame * pFrame);
    
    UINT GetFrameCount();
    CXFrame * GetFrameByIndex(UINT nIndex);
    UINT GetFrameIndex(CXFrame * pFrame);
    
    // of the class CXFrame
    

    Via these methods of CXFrame, we can add, remove child frames of a frame.

    Note that the order of child frames maters. Child frames of a frame will be drawn from the first one to the last one. A child frame drawn later appears on the top of the child frames of the same parent that are drawn earlier. InsertFrame inserts a child frame at a specified position and AddFrame inserts a child frame as the last child frame.

     

    Last edit: Leo 2013-09-09

Log in to post a comment.