Login


MFC Hex Editor Control

By Jonathan Wood on 12/30/2010
Language: C++
Technology: MFC
Platform: Windows
License: CPOL
Views: 30,108
Desktop Development » Controls » User Controls » MFC Hex Editor Control

Demo Project Screenshot

Download Source Code Download Source Code

Introduction

When I wrote my Cygnus Hex Editor application a number of years ago, I developed some fairly sophisticated memory structures that made manipulating large files more efficient. After all, if a user loaded a 15MB file and then started typing away at the start of the file in insert mode, there had to be a little sleight of hand going on in the background to ensure the program could stay responsive.

But I also had the need for a hex editor control that could be placed in dialog boxes. This editor wouldn't need to handle huge data files or support all the bells and whistles. Rather, it just needed to be able to perform basic editing operations on smaller amounts of data.

So I decided to create a custom control for this purpose. In this article, I'll present code derived from the custom control I wrote back then. While it's simple, it does support all the standard operations such as insert and overwrite modes, making selections with the keyboard or mouse, auto scroll (when the mouse drags outside the window), clipboard operations, etc.

The CHexEdit Custom Control

The code is too long to insert it all into this article. Listing 1 shows the header file declaration for my CHexEdit control. It derives directly from CWnd, so all of the control's functionality is basically written from scratch.

The static member m_bIsRegistered is initialized with the value returned from the static RegisterWndClass() method. A side effect of this arrangement is that the RegisterWndClass() method is called on the application's startup, which ensures the Windows class used by the control is registered before the control is ever used.

The control was designed to be used in an MFC dialog box and may require changes to use in other scenarios. It performs most all of its initialization in the CHexEdit constructor because MFC control instances never receive a WM_CREATE message. Instead, the control instance is attached to the control when the dialog is initialized. By this time, the control window has already been created.

One issue that came up during the design of the control was figuring out how to separate the private implementation from the public interface. I thought about creating two separate classes for this. However, MFC won't allow you to create two instances of Window classes that refer to the same window.

Next, I looked at creating a simple class for using the control, and then deriving another class from the first that implements the control. This way, both classes could be used to refer to the same class instance, and window. However, this raised the possibility of new problems.

To make a long story short, I managed to simplify things and keep everything in one class. It has some public members that can be used to control the class, in addition to the code that implements the controls internal functionality.

Listing 1: CHexEdit Class Declaration

class CHexEdit : public CWnd
{
public:
    CHexEdit(void);
    virtual ~CHexEdit(void);

    // Public interface members
    int GetDataLength() { return m_nLength; }
    int SetData(int nLength, BYTE* pBuffer);
    int GetData(int nMaxLen, BYTE* pBuffer);
    int GetColumn() { return m_bHexCol; }
    void SetColumn(int nColumn) { m_bHexCol = (nColumn != 0); }
    int GetReadOnly() { return m_bReadOnly; }
    void SetReadOnly(BOOL bReadOnly) { m_bReadOnly = bReadOnly; }
    ULONG GetSelection();
    void SetSelection(int nSelStart = 0, int nSelEnd = 0);
    int InsertData(int nCount);
    int DeleteData(int nCount);
    int GetPosition() { return m_nPosition; }
    int SetPosition(int nPos);
    BOOL GetWideView() { return (BOOL)(m_nBytesPerRow == 0x10); }
    void SetWideView(BOOL bWideView);
    BOOL GetInsertMode() { return m_bInsertMode; }
    void SetInsertMode(BOOL bMode, BOOL bToggle = TRUE);
    void LimitLength(int nLimit);
    void ShowAllAscii(BOOL bShowAllAscii);

protected:

    // Internal implementation members
    static BOOL RegisterWndClass();

    void SetDisplayMetrics();
    void ClearAll();
    int GetClientRows();
    int Insert(int nCount);
    int Delete(int nCount);

    int OffsetFromPoint(CPoint point);
    void GetCaretXY(POINT& point);
    void SetCaret(POINT& point);
    void SetCaret();
    void SetCurrPos(int nPosition, int nSelAction = HEX_SEL_CLEAR, int bInByte = 0);
    void SetScroll(int nTopPosition);

    BOOL HasSelection() { return m_nSelStart != m_nSelEnd; }
    int GetSelLength() { return m_nSelEnd - m_nSelStart; }
    void DeleteSelection();

    BOOL CanPaste();
    COleDataSource* CreateDataSource();
    BOOL DoPasteData(COleDataObject* pDataObject);

    void GetOffsetString(TCHAR* sBuffer, int nOffset);
    void GetHexString(TCHAR* sBuffer, BYTE* lpBuffer, int nCount);
    void GetChrString(TCHAR* sBuffer, BYTE* lpBuffer, int nCount);

    CSize GetClientSize();
    void Update(int nHint = HEX_HINT_ALL, int nStart = 0, int nEnd = 0);
    void UpdateScrollbar();

protected:

    // Data members
    static const TCHAR m_szWndClassName[];
    static UINT m_cfFormat;
    static BOOL m_bIsRegistered;

    BYTE* m_pBuffer;
    int m_nLength;

    int m_nPosition;
    int m_nTopPosition;
    BOOL m_bInByte;
    BOOL m_bCaretIsVisible;
    BOOL m_bTrailingCaret;
    int m_nSelStart, m_nSelEnd;
    int m_nSelAnchor;
    int m_nTimerID;

    CSize m_CharSize;
    COLUMN_METRICS m_ColMetrics[3];

    BOOL m_bInsertMode;
    BOOL m_bInsertToggle;
    BOOL m_bHexCol;
    BOOL m_bReadOnly;
    int m_nBytesPerRow;
    int m_nLimit;
    BOOL m_bShowAllChars;

protected:

    // Generated message map functions
    afx_msg void OnPaint();
    afx_msg UINT OnGetDlgCode();
    afx_msg void OnSetFocus(CWnd* pOldWnd);
    afx_msg void OnKillFocus(CWnd* pNewWnd);
    afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
    afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
    afx_msg void OnMouseMove(UINT nFlags, CPoint point);
    afx_msg void OnTimer(UINT nIDEvent);
    afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
    afx_msg void OnEditPaste();
    afx_msg void OnEditCopy();
    afx_msg void OnEditCut();
    afx_msg void OnEditDelete();
    afx_msg void OnContextMenu(CWnd* pWnd, CPoint point);

    DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnEditSelectAll();
};

Using the Control

As previously stated, the CHexEdit control was designed to be placed on a dialog box. To do this, start by adding a Custom Control resource to your dialog box.

Set the control's class to "SoftCircuitsHexEdit". This is the name that RegisterWndClass() used to register the Windows class. Also, we won't be using the Caption property so you can delete that.

I set the Style property to 0x50810000 and the Extended Style property to 0x0. You may want to use different styles to change the appearance or behavior of the control. However, it can be a little awkward when working with custom controls because the properties window doesn't provide as much help as it does for "known" controls.

Next, create a control variable by right clicking on the custom control and selecting the Add Variable command. Make sure Category is set to Control. For most controls, this step is optional and just makes it easier to interface with a dialog control. However, here, it's absolutely necessary. If you fail to do this step, the CHexEdit class is never instantiated, and the control will have no functionality.

Next, your project needs to define a menu resource with the ID IDR_HEXCTRLMENU. This is the context menu used by the control. Supported commands in this menu include ID_EDIT_CUT, ID_EDIT_COPY, ID_EDIT_PASTE, ID_EDIT_DELETE, and ID_EDIT_SELECT_ALL.

Finally, ensure that your application calls the MFC function AfxOleInit() at some point during initialization. This is needed by the hex editor control for the clipboard routines to work. If you have a large project, you may already be calling this function. Otherwise, you'll need to add a call to this function.

That should be about all you need. You can review the public class members at the top of HexEdit.h. You can call these methods on the control variable you created to interface with the control.

Note that the Tab key is used to switch between the hex and ASCII columns. To tab to the next control in the dialog box, you'll need to use Ctrl+Tab.

Conclusion

Where some characters can mess up a regular EDIT control, the hex editor control will handle any sequence of bytes. So this control works great for editing any type of binary data. Perhaps you can find a use for it in your projects.

End-User License

Use of this article and any related source code or other files is governed by the terms and conditions of The Code Project Open License.

Author Information

Jonathan Wood

I'm a software/website developer working out of the greater Salt Lake City area in Utah. I've developed many websites including Black Belt Coder, Insider Articles, and others.