Tabbed Views (2)

Many times in an SDI interface the programmer would like to have many views of the same document.  Look at the MFC sample collect.  It consists of an SDI app which has nine different views for each of the data types in its document.  The view is switched by selecting a menu item.  There are several other different ways this user interface could be organized and presented to the user. One would be by presenting a TabCtrl to the user and have a Tab for each view.  This is the approach I wanted to take in my own SDI application.  Following then will be the code for CTabCtrlView, a view which replaces the standard SDI client window and handles the displaying of the multiple view. An AVI of the control follows:

As an example of this control I will describe how to modify the MFC collect sample so it uses my new control as its interface.  The first thing you must do is derive your own class from CTabCtrlView.  There are several virtual functions you must override.  This allows you to add your own application specific Tab items and Send Message Commands.  The first function to override is InitTabs.  This function is responsible for inserting the Tabs into the TabCtrl.  Proceed as you would for any CTabCtrl object.:

void CMyTabView::InitTabs(CTabCtrlView* pView)
{
        TC_ITEM TabCtrlItem;

        TabCtrlItem.mask = TCIF_TEXT | TCIF_IMAGE;
        TabCtrlItem.pszText = "CStringList";
        TabCtrlItem.iImage = 1;
        m_TabCtl.InsertItem( 0, &TabCtrlItem );
        TabCtrlItem.pszText = "CTypedPtrList";
        m_TabCtl.InsertItem( 1, &TabCtrlItem );
        TabCtrlItem.pszText = "CList";
        m_TabCtl.InsertItem( 2, &TabCtrlItem );
        TabCtrlItem.pszText = "CDWordArray";
        m_TabCtl.InsertItem( 3, &TabCtrlItem );
        TabCtrlItem.pszText = "CTypedPtrArray";
        m_TabCtl.InsertItem( 4, &TabCtrlItem );
        TabCtrlItem.pszText = "CArray";
        m_TabCtl.InsertItem( 5, &TabCtrlItem );
        TabCtrlItem.pszText = "CMapStringToString";
        m_TabCtl.InsertItem( 6, &TabCtrlItem );
        TabCtrlItem.pszText = "CTypedPtrMap";
        m_TabCtl.InsertItem( 7, &TabCtrlItem );
        TabCtrlItem.pszText = "CMap";
        m_TabCtl.InsertItem( 8, &TabCtrlItem );
        //Important You must always call the base class        
        CTabCtrlView::InitTabs(pView);
        return;
}
The next step is to override the function HandleTabs().  This function will send a message back to the main window for each tab that needs processing.  The way MFC is written it is the job of the main frame to handle switching view and assigning the view. Instead of reinventing the wheel I decided it would be easier to send a message to the main frame and allow it to handle the changing of the views. The HandleTabs for the new MFC collect should look like this:
BOOL CMyTabView::HandleTabs(int sel)
{
        //The MFC doc/view model expects the
        //mainFrame to handle changing view
        //so we let the mainframe do it.

        CWnd* Wnd = AfxGetMainWnd();

        switch(sel)
        {
        case 0:
                Wnd->SendMessage(WM_COMMAND, ID_STRINGLIST, 0);
                break;
        case 1:
                Wnd->SendMessage(WM_COMMAND, ID_TYPEDLIST, 0);
                break;
        case 2:
                Wnd->SendMessage(WM_COMMAND, ID_INTLIST, 0);
                break;
        case 3:
                Wnd->SendMessage(WM_COMMAND, ID_DWORDARRAY, 0);
                break;
        case 4:
                Wnd->SendMessage(WM_COMMAND, ID_TYPEDPTRARRAY, 0);
                break;
        case 5:
                Wnd->SendMessage(WM_COMMAND, ID_POINTARRAY, 0);
                break;
        case 6:
                Wnd->SendMessage(WM_COMMAND, ID_MAPSTRINGTOSTRING, 0);
                break;
        case 7:
                Wnd->SendMessage(WM_COMMAND, ID_TYPEDPTRMAP, 0);
                break;
        case 8:
                Wnd->SendMessage(WM_COMMAND, ID_MAPDWORDTOMYSTRUCT, 0);
                break;
        default:
                ASSERT(0);
                return FALSE;
        }
        return TRUE;
}
As you notice each Tab sends the main frame the corresponding COMMAND message that the menu would have if the user would have selected a menu item.  This implementation allows you to be redundant by using tabs or the menu to change the view.

There needs to be a couple of changes to the main frame window code as well. The first thing is to make an protected object from your derived class. Now that the object exists we need to tell the main frame that this window will be its client window. Override the OnCreateClient function and change the function to look like this:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext) 
{
        if(!m_tabView.CreateStatic(this))
        {
                TRACE0("Failed to CreatePropertySheet\n");
                return FALSE;
        }       
        CRuntimeClass* pNewViewClass = RUNTIME_CLASS(CStringListView);
        m_tabView.SetTab(0);

        CSize size(160, 180);

        if (!m_tabView.CreateView(pNewViewClass, size, pContext))
        {
                TRACE0("Failed to create first pane\n");
                return FALSE;
        }
        
        SetActiveView(m_tabView.GetActiveView());
        return TRUE;
}

Now when the application creates the main client window your CTabCtrlView object should appear as the client window for your application. The final step is to change the existing OnExample function in the main frame window.  We need to tell it that when it creates a view it should call CTabCtrl's Create View and not the one for the main Frame. Also we call the TabCtrlView's RecalcLayout function which will correctly position the new view in the TabCtrl's window area.

void CMainFrame::OnExample(UINT nCmdID)
{
        if (nCmdID == m_nCurrentExample)
                return;  // already selected

        // Set the child window ID of the active view to AFX_IDW_PANE_FIRST.
        // This is necessary so that CFrameWnd::RecalcLayout will allocate
        // this "first pane" to that portion of the frame window's client
        // area not allocated to control bars.  Set the child ID of
        // the previously active view to some other ID; we will use the
        // command ID as the child ID.
        CView* pOldActiveView = GetActiveView();
                ::SetWindowLong(pOldActiveView->m_hWnd, GWL_ID, m_nCurrentExample);

        CRuntimeClass* pNewViewClass;
        switch (nCmdID)
        {
                case ID_STRINGLIST:
                        pNewViewClass = RUNTIME_CLASS(CStringListView);
                        break;
                case ID_TYPEDLIST:
                        pNewViewClass = RUNTIME_CLASS(CTypedPtrListView);
                        break;
                case ID_INTLIST:
                        pNewViewClass = RUNTIME_CLASS(CIntListView);
                        break;
                case ID_DWORDARRAY:
                        pNewViewClass = RUNTIME_CLASS(CDWordArrayView);
                        break;
                case ID_TYPEDPTRARRAY:
                        pNewViewClass = RUNTIME_CLASS(CTypedPtrArrayView);
                        break;
                case ID_POINTARRAY:
                        pNewViewClass = RUNTIME_CLASS(CPointArrayView);
                        break;
                case ID_MAPSTRINGTOSTRING:
                        pNewViewClass = RUNTIME_CLASS(CMapStringToStringView);
                        break;
                case ID_TYPEDPTRMAP:
                        pNewViewClass = RUNTIME_CLASS(CTypedPtrMapView);
                        break;
                case ID_MAPDWORDTOMYSTRUCT:
                        pNewViewClass = RUNTIME_CLASS(CMapDWordToMyStructView);
                        break;
                default:
                        ASSERT(0);
                        return;
        }

        // create the new view
        CCreateContext context;
        context.m_pNewViewClass = pNewViewClass;
        context.m_pCurrentDoc = GetActiveDocument();

        // New Code below
        CView* pNewView = m_tabView.CreateView(pNewViewClass, CSize(100,100), &context);

        if (pNewView != NULL)
        {
                // the new view is there, but invisible and not active...
                pNewView->ShowWindow(SW_SHOW);
                pNewView->OnInitialUpdate();
                SetActiveView(pNewView);
                m_tabView.RecalcLayout();       //<-- New Code
                RecalcLayout();
                m_nCurrentExample = nCmdID;

                // finally destroy the old view...
                pOldActiveView->DestroyWindow();
        }
}
That's about it, you should now have a version of the MFC collect sample which uses my CTabCtrlView Class.   Now for a few words on the class. It consists of two main parts.  A wrapper window which gets set as the main frame's client and the TabCtrl itself.  It is the job of the wrapper window to resize the TabCtrl and the view to keep everything in its proper place. The window also handles the initialization and handling of the Tabs for the TabCtrl. The code for this class follows:
 
#if !defined(AFX_CTabCtrlView_H__8E652EC1_5159_11D1_96A0_0E6B8A000000__INCLUDED_)
#define AFX_CTabCtrlView_H__8E652EC1_5159_11D1_96A0_0E6B8A000000__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000
// CTabCtrlView.h : header file
//

#include "afxcmn.h"

class CTabCtrlView;

class CViewTabCtl : public CTabCtrl
{
// Construction
public:
        CViewTabCtl();

// Attributes
public:

// Operations
public:
        
// Overrides
        // ClassWizard generated virtual function overrides
        //{{AFX_VIRTUAL(CViewTabCtl)
        //}}AFX_VIRTUAL

// Implementation
public:
        void SetView(CTabCtrlView* pView);
        virtual void RecalcLayout(CRect& rect, CWnd* wnd);
        virtual ~CViewTabCtl();
        virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);

        // Generated message map functions
protected:
        CTabCtrlView* m_pView;
        CString m_sGrayFont;
        CString m_sSelFont;
        CDC m_dc;
        CFont m_selFont;
        virtual BOOL HandleTabs(int sel);
        //{{AFX_MSG(CViewTabCtl)
        afx_msg void OnSelchange(NMHDR* pNMHDR, LRESULT* pResult);
        //}}AFX_MSG

        DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// CCTabCtrlView window

// The actual tab control
// Wrapper Window Handles Interfacing to MFC doc View Model
// and resizing of the Tab Ctrl
class CTabCtrlView : public CWnd
{
// Construction
public:
        CTabCtrlView();

// Attributes
public:

// Operations
public:

// Overrides
        // ClassWizard generated virtual function overrides
        //{{AFX_VIRTUAL(CCTabCtrlView)
        //}}AFX_VIRTUAL

// Implementation
public:
        virtual BOOL HandleTabs(int sel);
        void SetTab(int Tab);
        virtual void SetView();
        void RecalcLayout();
        CView* GetActiveView();
        BOOL CreateStatic(CWnd* pParentWnd, DWORD dwStyle = WS_CHILD | WS_VISIBLE, UINT nID = AFX_IDW_PANE_FIRST);
        virtual CView* CreateView(CRuntimeClass* pViewClass, SIZE sizeInit, CCreateContext* pContext );
        virtual ~CTabCtrlView();

        // Generated message map functions
protected:
        virtual void InitTabs(CTabCtrlView* pView);
        CViewTabCtl m_TabCtl;
        CView* m_ActiveView;
        
        BOOL CreateCommon(CWnd* pParentWnd, SIZE sizeMin, DWORD dwStyle, UINT nID);
        //{{AFX_MSG(CCTabCtrlView)
        afx_msg void OnSize(UINT nType, int cx, int cy);
        afx_msg BOOL OnEraseBkgnd(CDC* pDC);
        afx_msg void OnPaint();
        //}}AFX_MSG

        DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////
// CViewTabCtl window


//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_CTabCtrlView_H__8E652EC1_5159_11D1_96A0_0E6B8A000000__INCLUDED_)


/////////////////////////////////////////////////////////////////////////////
C++ Code file
/////////////////////////////////////////////////////////////////////////////

// CTabCtrlView.cpp : implementation file
//

// core headers
#include "afx.h"
#include "afxplex_.h"
#include "afxcoll.h"
#include "afxcmn.h"

#include "stdafx.h"

#include "TabCtrlView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define CX_BORDER 1
#define CY_BORDER 1

#define MAX_COLORS 10

// The following colors give a Tab Ctrl an OS/2 look
COLORREF colorRef[MAX_COLORS] =
{
        RGB(0,225,255),
        RGB(0,240,190),
        RGB(128,128,255),
        RGB(240,200,175),
        RGB(240,240,150),
        RGB(175,130,175),
        RGB(240,140,0),
        RGB(255,200,0),
        RGB(255,160,120),
        RGB(255,200, 175)
};


/////////////////////////////////////////////////////////////////////////////
// CTabCtrlView

CTabCtrlView::CTabCtrlView()
{
}

CTabCtrlView::~CTabCtrlView()
{
}


BEGIN_MESSAGE_MAP(CTabCtrlView, CWnd)
        //{{AFX_MSG_MAP(CTabCtrlView)
        ON_WM_SIZE()
        ON_WM_ERASEBKGND()
        ON_WM_PAINT()
        //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CTabCtrlView message handlers

BOOL CTabCtrlView::CreateStatic(CWnd * pParentWnd, DWORD dwStyle, UINT nID)
{
        ASSERT(pParentWnd != NULL);
        ASSERT(dwStyle & WS_CHILD);
        ASSERT(!(dwStyle & SPLS_DYNAMIC_SPLIT));

        // create with zero minimum pane size
        if (!CreateCommon(pParentWnd, CSize(0, 0), dwStyle, nID))
                return FALSE;

        // all panes must be created with explicit calls to CreateView
        return TRUE;
}





BOOL CTabCtrlView::CreateCommon(CWnd * pParentWnd, SIZE sizeMin, DWORD dwStyle, UINT nID)
{
        ASSERT(pParentWnd != NULL);
        ASSERT(sizeMin.cx >= 0 && sizeMin.cy >= 0);
        ASSERT(dwStyle & WS_CHILD);
        ASSERT(nID != 0);

        // create with the same wnd-class as MDI-Frame (no erase bkgnd)
        if (!CreateEx(0, NULL, NULL, dwStyle, 0, 0, 0, 0,
          pParentWnd->m_hWnd, (HMENU)nID, NULL))
                return FALSE;       // create invisible

        //Create the Tab Control
        CRect rect;
        GetClientRect(rect);
        CImageList pImageList;


        m_TabCtl.Create(WS_VISIBLE | WS_CHILD | TCS_OWNERDRAWFIXED, rect, this, nID);

        //Overide this function to provide your Tabs
        InitTabs(this);
        return TRUE;
}

CView* CTabCtrlView::CreateView(CRuntimeClass * pViewClass, SIZE sizeInit, CCreateContext * pContext)
{
#ifdef _DEBUG
        ASSERT_VALID(this);
        ASSERT(pViewClass != NULL);
        ASSERT(pViewClass->IsDerivedFrom(RUNTIME_CLASS(CWnd)));
        ASSERT(AfxIsValidAddress(pViewClass, sizeof(CRuntimeClass), FALSE));
#endif

        BOOL bSendInitialUpdate = FALSE;

        CCreateContext contextT;
        if (pContext == NULL)
        {
                // if no context specified, generate one from the currently selected
                //  client if possible
                CView* pOldView = NULL;
                if (pOldView != NULL && pOldView->IsKindOf(RUNTIME_CLASS(CView)))
                {
                        // set info about last pane
                        ASSERT(contextT.m_pCurrentFrame == NULL);
                        contextT.m_pLastView = pOldView;
                        contextT.m_pCurrentDoc = pOldView->GetDocument();
                        if (contextT.m_pCurrentDoc != NULL)
                                contextT.m_pNewDocTemplate =
                                  contextT.m_pCurrentDoc->GetDocTemplate();
                }
                pContext = &contextT;
                bSendInitialUpdate = TRUE;
        }

        CWnd* pWnd;
        TRY
        {
                pWnd = (CWnd*)pViewClass->CreateObject();
                if (pWnd == NULL)
                        AfxThrowMemoryException();
        }
        CATCH_ALL(e)
        {
                TRACE0("Out of memory creating a splitter pane.\n");
                // Note: DELETE_EXCEPTION(e) not required
                return (CView*) NULL;
        }
        END_CATCH_ALL

        ASSERT_KINDOF(CWnd, pWnd);
        ASSERT(pWnd->m_hWnd == NULL);       // not yet created

        DWORD dwStyle = AFX_WS_DEFAULT_VIEW;

        // Create with the right size (wrong position)
        CRect rect(CPoint(0,0), sizeInit);
        if (!pWnd->Create(NULL, NULL, dwStyle,
                rect, this, 0, pContext))
        {
                TRACE0("Warning: couldn't create client pane for splitter.\n");
                        // pWnd will be cleaned up by PostNcDestroy
                return (CView*) NULL;
        }

        // send initial notification message
        if (bSendInitialUpdate);
//              pWnd->SendMessage(WM_INITIALUPDATE);
        m_ActiveView = (CView*) pWnd;
        return m_ActiveView;
}

void CTabCtrlView::OnSize(UINT nType, int cx, int cy) 
{
        if (nType != SIZE_MINIMIZED && cx > 0 && cy > 0)
                RecalcLayout();

        CWnd::OnSize(nType, cx, cy);
        return;
}

void CTabCtrlView::RecalcLayout()
{
        CWnd* pWnd = (CWnd*) GetActiveView();
        CRect rect;
        GetClientRect(&rect);
        m_TabCtl.RecalcLayout(rect, pWnd);
}

CView* CTabCtrlView::GetActiveView()
{
        return m_ActiveView;
}

BOOL CTabCtrlView::OnEraseBkgnd(CDC* pDC) 
{
        return FALSE;
}

void CTabCtrlView::OnPaint() 
{
        CPaintDC dc(this); // device context for painting
}

void CTabCtrlView::SetView()
{
        //In most cases your main app window
        //should handle this. This is becuase
        //the doc view model expects the view
        //to be attached to your main frame
}

void CTabCtrlView::SetTab(int Tab)
{
        m_TabCtl.SetCurSel(Tab);
}

void CTabCtrlView::InitTabs(CTabCtrlView* pView)
{
        m_TabCtl.SetView(pView);
        return;
}

BOOL CTabCtrlView::HandleTabs(int sel)
{
        ASSERT(FALSE);
        return FALSE;
}






/////////////////////////////////////////////////////////////////////////////
// CViewTabCtl

CViewTabCtl::CViewTabCtl()
{
        m_sSelFont = _T("Helv");
        m_sGrayFont= _T("Helv");
}

CViewTabCtl::~CViewTabCtl()
{
}


BEGIN_MESSAGE_MAP(CViewTabCtl, CTabCtrl)
        //{{AFX_MSG_MAP(CViewTabCtl)
        ON_NOTIFY_REFLECT(TCN_SELCHANGE, OnSelchange)
        //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CViewTabCtl message handlers

void CViewTabCtl::OnSelchange(NMHDR* pNMHDR, LRESULT* pResult) 
{
        // TODO: Add your control notification handler code here
        int nSel = GetCurSel();
        
        HandleTabs(nSel);
        
        *pResult = 0;
}

BOOL CViewTabCtl::HandleTabs(int sel)
{
        return m_pView->HandleTabs(sel);
}

void CViewTabCtl::RecalcLayout(CRect & rect, CWnd * wnd)
{
        SetWindowPos(NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER);

        int ind = GetCurSel();
        AdjustRect(FALSE, &rect);
        wnd->SetWindowPos(NULL, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER);
}

void CViewTabCtl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
        int nSel = lpDrawItemStruct->itemID;
        ASSERT(nSel > -1);
        ASSERT(nSel <  GetItemCount()); 

        TC_ITEM item;
        char text[255];

        item.pszText = text;
        item.mask = TCIF_TEXT;
        GetItem(nSel, &item);

        if(!m_dc.Attach(lpDrawItemStruct->hDC))
                return;

        CRect rect = CRect(&(lpDrawItemStruct->rcItem));

        rect.NormalizeRect();
        rect.DeflateRect(CX_BORDER, CY_BORDER);

        CBrush brush(colorRef[nSel - (nSel / MAX_COLORS) * MAX_COLORS]);
        
        m_dc.FillRect(rect, &brush);

        
        COLORREF tcolor;
        if (nSel == GetCurSel())
        {
                m_selFont.DeleteObject();
                m_selFont.CreatePointFont(100, LPCTSTR(m_sGrayFont), &m_dc);
                m_dc.SelectObject(m_selFont);
                tcolor = RGB(0,0,0);
                
        }
        else
        {
                m_selFont.DeleteObject();
                m_selFont.CreatePointFont(80, LPCTSTR(m_sSelFont), &m_dc);
                m_dc.SelectObject(m_selFont);
                tcolor = GetSysColor(COLOR_3DSHADOW);
        }
        m_dc.SetBkMode(TRANSPARENT);
        m_dc.SetTextColor(tcolor);
        m_dc.DrawText(text, rect, DT_VCENTER|DT_CENTER);
        m_dc.Detach();
        return;
}

void CViewTabCtl::SetView(CTabCtrlView * pView)
{
        m_pView = pView;
        return;
}



Comments

  • real mlb jersey

    Posted by hlgernoneovh on 03/12/2013 12:38pm

    clarisonic mia 2 outlet clarisonic sale clarisonic online store cheap clarisonic online cheap clarisonic sale canada clarisonic mia 2 canada cheap clarisonic uk clarisonic mia 2 uk authentic mlb jersey wholesale nhl jerseys

    Reply
  • What about MDI?

    Posted by Legacy on 06/19/2003 12:00am

    Originally posted by: Rudolf

    I tried to accomodate the code in the MDI application and I can not get it to work at all. Any examples how to do that in MDI?

    Reply
  • CTabCtrlView::CreateView confusion

    Posted by Legacy on 12/11/2002 12:00am

    Originally posted by: Stephen Zurcher

    Here's a chunk from the CreateView function implementation:
    
    BOOL bSendInitialUpdate = FALSE;

    CCreateContext contextT;
    if (pContext == NULL)
    {
    // if no context specified, generate one from the
    // currently selected
    // client if possible
    CView* pOldView = NULL;
    if (pOldView != NULL &&
    pOldView->IsKindOf(RUNTIME_CLASS(CView)))

    My confusion arises from the creation of pOldView,
    initialized NULL, followed immediately by a test case
    stating if it is not null, do something... is this just
    paranoia, or how is it possible for the pOldView variable,
    just created NULL, to not be NULL one instruction later?

    Did I miss a side-effect somewhere? should it instead be
    testing if it is equal to NULL (pOldView == NULL)?

    Reply
  • Project demo

    Posted by Legacy on 12/27/2001 12:00am

    Originally posted by: chen

    It is quite nice. But could you provide a project demo for download?
    Thanks

    Reply
  • CMyTabView::InitTabs(....)

    Posted by Legacy on 09/27/1999 12:00am

    Originally posted by: Sajjid

    Never Mind!

    Reply
  • Bug in DrawItem

    Posted by Legacy on 05/06/1999 12:00am

    Originally posted by: Norm Almond

    When trying your excellent example under VC6.0. DrawItem fails
    because the item.cchTextMax has not be assigned the size of
    the buffer receiving the string.

    void CViewTabCtl::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
    {
    int nSel = lpDrawItemStruct->itemID;
    ASSERT(nSel > -1);
    ASSERT(nSel < GetItemCount());

    TC_ITEM item;
    char text[255];

    item.pszText = text;
    item.mask = TCIF_TEXT;

    /* F I x - Assign sizeof buffer */
    item.cchTextMax = sizeof(text);

    GetItem(nSel, &item);

    Reply
  • What is OnExmaple function?

    Posted by Legacy on 03/12/1999 12:00am

    Originally posted by: KangSeungBum

    When or where OnExample function will be called?
    

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • IBM Worklight is a mobile application development platform that lets you extend your business to mobile devices. It is designed to provide an open, comprehensive platform to build, run and manage HTML5, hybrid and native mobile apps.

  • New IT trends to support worker mobility — such as VDI and BYOD — are quickly gaining interest and adoption. But just as with any new trend, there are concerns and pitfalls to avoid.  Download this paper to learn the most important considerations to keep in mind for your VDI project.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds