#include "stdafx.h" #include "HexEditCtrl.h" #include CHexEditCtrl::CHexEditCtrl(void) : m_BaseAddress(0x80000000), m_DrawnBaseAddress(0xFFFFFFFF), m_SelStartAddress(0), m_SelEndAddress(0), m_SelStartCellSide(HX_LEFT), m_SelEndCellSide(HX_LEFT), m_bInsertMode(false), m_CaretAddress(0), m_bCaretLoNibble(false), m_bCaretVisible(false), m_bHaveCaret(false), m_bShowHotAddress(false), m_HotAddress(0), m_Font(nullptr), m_BackBMP(nullptr), m_BackDC(nullptr), m_CharWidth(0), m_CharHeight(0), m_FocusedColumn(HX_COL_NONE), m_hCursorIBeam(nullptr), m_hCursorDefault(nullptr), m_DragScrollDelta(0), m_AddressColumnRect({0}), m_HexDataColumnRect({0}), m_AsciiColumnRect({0}), m_bDblClicked(false), m_bLButtonDown(false), m_bMouseDragging(false), m_bLayoutChanged(false), m_OldBytes(nullptr), m_NewBytes(nullptr), m_NumBytesPerGroup(4), m_NumByteGroupsPerRow(0), m_NumVisibleRows(0), m_NumVisibleBytes(0), m_RealSelStartAddress(0), m_RealSelEndAddress(0), m_bHaveRealSel(false) { WNDCLASS wc; if (!GetClassInfo(GetModuleHandle(nullptr), _T("HexEditCtrl"), &wc)) { GetWndClassInfo().m_wc.lpfnWndProc = m_pfnSuperWindowProc; GetWndClassInfo().Register(&m_pfnSuperWindowProc); } } CHexEditCtrl::~CHexEditCtrl(void) { } int CALLBACK CHexEditCtrl::HaveFontCb(CONST LOGFONTW * lplf, CONST TEXTMETRICW * /*lptm*/, DWORD /*FontType*/, LPARAM lParam) { const wchar_t * name = (const wchar_t *)lParam; if (wcscmp(lplf->lfFaceName, name) == 0) { return 0; } return 1; } bool CHexEditCtrl::HaveFont(HDC hdc, const char * name) { if (EnumFonts(hdc, stdstr(name).ToUTF16().c_str(), HaveFontCb, (LPARAM)stdstr(name).ToUTF16().c_str()) == 0) { return true; } return false; } BOOL CHexEditCtrl::Attach(HWND hWnd) { if (m_hWnd != nullptr) { return FALSE; } if (!CWindowImpl::SubclassWindow(hWnd)) { return FALSE; } CRect wndRc; if (!GetWindowRect(&wndRc)) { return FALSE; } HDC hdc = GetDC(); HBITMAP hOldBMP; HFONT hOldFont; m_BackDC = CreateCompatibleDC(hdc); m_BackBMP = CreateCompatibleBitmap(hdc, wndRc.Width(), wndRc.Height()); hOldBMP = (HBITMAP)SelectObject(m_BackDC, m_BackBMP); DeleteObject(hOldBMP); m_hCursorIBeam = LoadCursor(nullptr, IDC_IBEAM); m_hCursorDefault = LoadCursor(nullptr, IDC_ARROW); float dpiScale = ::GetDeviceCaps(hdc, LOGPIXELSX) / 96.0f; if (HaveFont(hdc, "Consolas")) { m_Font = CreateFont((int)(14 * dpiScale), 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE | FIXED_PITCH, L"Consolas"); } else { m_Font = (HFONT)GetStockObject(ANSI_FIXED_FONT); } hOldFont = (HFONT)SelectObject(m_BackDC, m_Font); DeleteObject(hOldFont); TEXTMETRIC tm; GetTextMetrics(m_BackDC, &tm); m_CharHeight = tm.tmHeight; m_CharWidth = tm.tmAveCharWidth; UpdateLayoutInfo(); ReallocByteBuffers(); CRect clrRc(0, 0, wndRc.Width(), wndRc.Height()); HBRUSH hbrush = CreateSolidBrush(BKCOLOR_DEFAULT); FillRect(m_BackDC, clrRc, hbrush); DeleteObject(hbrush); SetTimer(TIMER_ID_AUTO_REFRESH, 20, nullptr); SetTimer(TIMER_ID_DRAG_SCROLL, 50, nullptr); ReleaseDC(hdc); return TRUE; } HWND CHexEditCtrl::Detach(void) { if (m_hWnd == nullptr) { return nullptr; } KillTimer(TIMER_ID_AUTO_REFRESH); KillTimer(TIMER_ID_DRAG_SCROLL); if (m_BackBMP != nullptr) { DeleteObject(m_BackBMP); m_BackBMP = nullptr; } if (m_BackDC != nullptr) { DeleteObject(m_BackDC); m_BackDC = nullptr; } if (m_Font != nullptr) { DeleteObject(m_Font); m_Font = nullptr; } if (m_NewBytes != nullptr) { free(m_NewBytes); m_NewBytes = nullptr; } if (m_OldBytes != nullptr) { free(m_OldBytes); m_OldBytes = nullptr; } return CWindowImpl::UnsubclassWindow(); } void CHexEditCtrl::Draw(void) { Notify(HXN_REDRAWSTARTED); int startCellIndex = 0; uint32_t startAddress = m_BaseAddress; int numBytesToUpdate = m_NumVisibleBytes; bool bIgnoreDiff = false; if (m_BaseAddress != m_DrawnBaseAddress) { m_bShowHotAddress = false; int64_t addrDelta = (int64_t)m_BaseAddress - (int64_t)m_DrawnBaseAddress; // Scroll optimization if ((addrDelta % m_NumBytesPerRow) == 0 && abs(addrDelta) < (m_NumVisibleBytes - m_NumBytesPerRow)) { int rowDelta = (int)(addrDelta / m_NumBytesPerRow); int numBytesScrolled = abs(rowDelta) * m_NumBytesPerRow; int numBytesToShift = (m_NumVisibleBytes - numBytesScrolled) - m_NumBytesPerRow; int shiftSrcIndex = 0, shiftDstIndex = 0; numBytesToUpdate = numBytesScrolled + m_NumBytesPerRow; CRect rcScrollArea; rcScrollArea.left = m_HexDataColumnRect.left; rcScrollArea.right = m_AsciiColumnRect.right; rcScrollArea.top = m_HexDataColumnRect.top; rcScrollArea.bottom = m_HexDataColumnRect.bottom; if (rowDelta > 0) { rcScrollArea.bottom -= m_CharHeight; startCellIndex = m_NumVisibleBytes - numBytesToUpdate; shiftSrcIndex = 0 + numBytesScrolled; shiftDstIndex = 0; } else if (rowDelta < 0) { rcScrollArea.top += m_CharHeight; startCellIndex = 0; shiftSrcIndex = 0 + m_NumBytesPerRow; shiftDstIndex = shiftSrcIndex + numBytesScrolled; } startAddress = m_BaseAddress + startCellIndex; memmove(&m_OldBytes[shiftDstIndex], &m_OldBytes[shiftSrcIndex], numBytesToShift * sizeof(HXBYTEINFO)); memmove(&m_NewBytes[shiftDstIndex], &m_NewBytes[shiftSrcIndex], numBytesToShift * sizeof(HXBYTEINFO)); ScrollDC(m_BackDC, 0, -rowDelta * m_CharHeight, &rcScrollArea, &rcScrollArea, nullptr, nullptr); InvalidateRect(&rcScrollArea, false); } DrawAddressColumn(); DrawHeader(); m_DrawnBaseAddress = m_BaseAddress; bIgnoreDiff = true; } if (m_bLayoutChanged) { bIgnoreDiff = true; m_bLayoutChanged = false; } int numOverflowBytes = 0; if (startAddress + numBytesToUpdate < startAddress) { numOverflowBytes = (startAddress + numBytesToUpdate); } for (int i = 0; i < numOverflowBytes; i++) { m_NewBytes[numBytesToUpdate - numOverflowBytes + i].bHidden = true; } NotifyGetByteInfo(startAddress, numBytesToUpdate - numOverflowBytes, bIgnoreDiff, &m_OldBytes[startCellIndex], &m_NewBytes[startCellIndex]); std::unordered_map drawnByteRects; for (int i = 0; i < numBytesToUpdate; i++) { uint32_t address = startAddress + i; HXBYTEINFO * oldByte = &m_OldBytes[startCellIndex + i]; HXBYTEINFO * newByte = &m_NewBytes[startCellIndex + i]; if (IsSelected(address)) { // Override owner-provided colors if selected if (newByte->bkColor != BKCOLOR_DEFAULT) { // Blend owner color with selection color if bkcolor isn't default newByte->bkColor = BlendColor(BKCOLOR_SEL_FOCUSED, newByte->bkColor); newByte->color = COLOR_SEL_FOCUSED; } else { newByte->bkColor = BKCOLOR_SEL_FOCUSED; newByte->color = COLOR_SEL_FOCUSED; } } if (address == m_HotAddress && m_bShowHotAddress && !m_bMouseDragging) { newByte->bkColor = BlendColor(BKCOLOR_HOT, newByte->bkColor); } // Redraw cell if value or colors have changed if (*newByte != *oldByte) { CRect rcHex, rcAscii; GetHexCellPos(startCellIndex + i, &rcHex); GetAsciiCellPos(startCellIndex + i, &rcAscii); // Check if a similar HXBYTEINFO has already been drawn std::unordered_map::const_iterator drawnByte = drawnByteRects.find(*newByte); if (drawnByte != drawnByteRects.end()) { HXRECTPAIR src = drawnByte->second; BitBlt(m_BackDC, rcHex.left, rcHex.top, src.rcHex.Width(), src.rcHex.Height(), m_BackDC, src.rcHex.left, src.rcHex.top, SRCCOPY); BitBlt(m_BackDC, rcAscii.left, rcAscii.top, src.rcAscii.Width(), src.rcAscii.Height(), m_BackDC, src.rcAscii.left, src.rcAscii.top, SRCCOPY); InvalidateRect(&rcHex, false); InvalidateRect(&rcAscii, false); } else if (newByte->bHidden) { HXRECTPAIR rectPair; Text(rcHex.left, rcHex.top, " ", BKCOLOR_DEFAULT, BKCOLOR_DEFAULT, &rectPair.rcHex); Text(rcAscii.left, rcAscii.top, " ", BKCOLOR_DEFAULT, BKCOLOR_DEFAULT, &rectPair.rcAscii); drawnByteRects[*newByte] = rectPair; } else { COLORREF hexBkColor = newByte->bkColor; COLORREF hexColor = newByte->color; COLORREF asciiBkColor = newByte->bkColor; COLORREF asciiColor = newByte->color; if (IsSelected(address)) { if (m_FocusedColumn == HX_COL_ASCII) { hexBkColor = BKCOLOR_SEL_UNFOCUSED; hexColor = COLOR_SEL_UNFOCUSED; } else { asciiBkColor = BKCOLOR_SEL_UNFOCUSED; asciiColor = COLOR_SEL_UNFOCUSED; } } HXRECTPAIR rectPair; if (newByte->bValid) { char szHexByte[4], szAsciiByte[2]; sprintf(szHexByte, "%02X", newByte->value); sprintf(szAsciiByte, "%c", ByteAscii(newByte->value)); Text(rcHex.left, rcHex.top, szHexByte, hexBkColor, hexColor, &rectPair.rcHex); Text(rcAscii.left, rcAscii.top, szAsciiByte, asciiBkColor, asciiColor, &rectPair.rcAscii); } else { Text(rcHex.left, rcHex.top, "**", hexBkColor, hexColor, &rectPair.rcHex); Text(rcAscii.left, rcAscii.top, ".", asciiBkColor, asciiColor, &rectPair.rcAscii); } drawnByteRects[*newByte] = rectPair; } } *oldByte = *newByte; } UpdateCaretUI(false); } void CHexEditCtrl::HitTest(int x, int y, HXHITTEST * pht) { memset(pht, 0, sizeof(HXHITTEST)); pht->column = HX_COL_NONE; CPoint pt(x, y); if (PtInRect(&m_AddressColumnRect, pt)) { pht->column = HX_COL_ADDRESS; } int headerHeight = m_CharHeight; // Clamp row int row = (y - headerHeight) / m_CharHeight; row = max(0, row); row = min(m_NumVisibleRows - 1, row); uint32_t rowAddress = SatAdd32(m_BaseAddress, row * m_NumBytesPerRow); if (x >= m_HexDataColumnRect.left && x < m_HexDataColumnRect.right) { if (PtInRect(&m_HexDataColumnRect, pt)) { pht->column = HX_COL_HEXDATA; } int groupWidth = (m_NumBytesPerGroup * m_CharWidth * 2) + (m_CharWidth * 1); int nGroup = (x - m_HexDataColumnRect.left) / groupWidth; int groupX = m_HexDataColumnRect.left + nGroup * groupWidth; int groupCharIdx = (x - groupX) / (m_CharWidth); uint32_t address = SatAdd32(rowAddress, nGroup * m_NumBytesPerGroup + groupCharIdx / 2); pht->hexAddress = address; pht->hexCellSide = (groupCharIdx & 1) ? HX_RIGHT : HX_LEFT; // TODO: Fix for wrap pht->asciiAddress = address; // Approximate pht->asciiCellSide = HX_LEFT; } else if (x >= m_AsciiColumnRect.left && x < m_AsciiColumnRect.right) { if (PtInRect(&m_AsciiColumnRect, pt)) { pht->column = HX_COL_ASCII; } int asciiX = x - m_AsciiColumnRect.left; int idx = (asciiX / m_CharWidth); pht->asciiAddress = SatAdd32(rowAddress, idx); pht->asciiCellSide = ((asciiX % m_CharWidth) > (m_CharWidth / 2)) ? HX_RIGHT : HX_LEFT; pht->hexAddress = SatAdd32(rowAddress, (m_NumBytesPerRow - 1)); // Approximate pht->hexCellSide = HX_RIGHT; } else if (x < m_HexDataColumnRect.left) { // Approximate pht->hexAddress = rowAddress; pht->hexCellSide = HX_LEFT; pht->asciiAddress = rowAddress; pht->asciiCellSide = HX_LEFT; } else if (x >= m_AsciiColumnRect.right) { // Approximate pht->hexAddress = SatAdd32(rowAddress, (m_NumBytesPerRow - 1)); pht->hexCellSide = HX_RIGHT; pht->asciiAddress = SatAdd32(rowAddress, (m_NumBytesPerRow - 1)); pht->asciiCellSide = HX_RIGHT; } } bool CHexEditCtrl::UpdateCaretUI(bool bEnsureVisible, bool bTop) { if (bEnsureVisible) { EnsureCaretAddressVisible(bTop); } if (!m_bHaveCaret) { return false; } if (!IsCaretAddressVisible()) { HideCaret(); return false; } ShowCaret(); int index = m_CaretAddress - m_BaseAddress; CRect rcCell; int xoffs = 0; if (m_FocusedColumn == HX_COL_ASCII) { if ((int)((m_RealSelEndAddress - m_BaseAddress) % m_NumBytesPerRow) == m_NumBytesPerRow - 1) { // Left-to-right selection ends on the end of a row index--; xoffs = m_CharWidth; } GetAsciiCellPos(index, &rcCell); SetCaretPos(rcCell.left + xoffs, rcCell.top); } else { if (GetSelDirection() > 0) { if ((int)((m_RealSelEndAddress - m_BaseAddress) % m_NumBytesPerRow) == m_NumBytesPerRow - 1) { // Left-to-right selection ends on the end of a row index--; xoffs = m_CharWidth * 2; } else if ((int)((m_RealSelEndAddress - m_BaseAddress) % m_NumBytesPerGroup) == m_NumBytesPerGroup - 1) { // Left-to-right selection ends on the end of a group xoffs = -m_CharWidth; } } GetHexCellPos(index, &rcCell); SetCaretPos(rcCell.left + (m_bCaretLoNibble ? m_CharWidth : 0) + xoffs, rcCell.top); } return true; } void CHexEditCtrl::Text(int x, int y, const char * text, COLORREF bg, COLORREF fg, CRect * rcOut) { std::wstring textOuput = stdstr(text).ToUTF16(CP_ACP); size_t length = textOuput.length(); int calcWidth = (int)((INT_PTR)length) * m_CharWidth; CRect rc(x, y, 0, 0); COLORREF orgBg = ::SetBkColor(m_BackDC, bg); COLORREF orgFg = ::SetTextColor(m_BackDC, fg); ::DrawText(m_BackDC, textOuput.c_str(), -1, &rc, DT_TOP | DT_NOPREFIX | DT_CALCRECT); rc.right = rc.left + calcWidth; // Just in case ::DrawText(m_BackDC, textOuput.c_str(), -1, &rc, DT_TOP | DT_NOPREFIX); InvalidateRect(&rc, false); ::SetBkColor(m_BackDC, orgBg); ::SetBkColor(m_BackDC, orgFg); *rcOut = rc; } void CHexEditCtrl::UpdateRealSelection(void) { uint32_t start = m_SelStartAddress; uint32_t end = m_SelEndAddress; bool bHaveSel = true; if (start < end) { if (m_SelEndCellSide == HX_LEFT) end--; if (m_SelStartCellSide == HX_RIGHT) start++; } else if (end < start) { if (start - end == 1) { if (m_SelStartCellSide == HX_LEFT && m_SelEndCellSide == HX_RIGHT) { bHaveSel = false; } } if (m_SelStartCellSide == HX_LEFT) start--; if (m_SelEndCellSide == HX_RIGHT) end++; swap(start, end); } else if (start == end) { if (m_SelStartCellSide == m_SelEndCellSide) { bHaveSel = false; } } if (m_RealSelStartAddress != start || m_RealSelEndAddress != end || m_bHaveRealSel != bHaveSel) { m_bHaveRealSel = bHaveSel; m_RealSelStartAddress = start; m_RealSelEndAddress = end; Notify(HXN_SELCHANGED); } } bool CHexEditCtrl::IsSelected(uint32_t address) { return m_bHaveRealSel && (address >= m_RealSelStartAddress && address <= m_RealSelEndAddress); } void CHexEditCtrl::DrawAddressColumn() { int headerHeight = m_CharHeight; for (int nRow = 0; nRow < m_NumVisibleRows; nRow++) { CRect rcAddress; uint32_t rowAddress = m_BaseAddress + (nRow * m_NumBytesPerRow); int y = headerHeight + nRow * m_CharHeight; if (rowAddress >= m_BaseAddress) { Text(0, y, stdstr_f(" %08X ", rowAddress).c_str(), BKCOLOR_ADDR, COLOR_ADDR, &rcAddress); } else { // Wrapped Text(0, y, " ", BKCOLOR_ADDR, COLOR_ADDR, &rcAddress); } } } void CHexEditCtrl::DrawHeader() { CRect rcClient; GetClientRect(&rcClient); CRect rcHeader = {0, 0, rcClient.Width(), m_CharHeight}; HBRUSH br = CreateSolidBrush(BKCOLOR_ADDR); FillRect(m_BackDC, &rcHeader, br); DeleteObject(br); int groupWidth = m_NumBytesPerGroup * m_CharWidth * 2 + m_CharWidth; for (int nGroup = 0; nGroup < m_NumByteGroupsPerRow; nGroup++) { int groupX = m_HexDataColumnRect.left + nGroup * groupWidth; int offs = nGroup * m_NumBytesPerGroup; CRect dummy; Text(groupX, 0, stdstr_f("%02X", offs).c_str(), BKCOLOR_ADDR, COLOR_ADDR, &dummy); } InvalidateRect(&rcHeader, false); } void CHexEditCtrl::GetHexCellPos(int index, CRect * rc) { int nRow = index / m_NumBytesPerRow; int rowOffs = (index % m_NumBytesPerRow); int nGroup = rowOffs / m_NumBytesPerGroup; int byteOffs = rowOffs % m_NumBytesPerGroup; int addrColumnWidth = (m_CharWidth * 11); int byteWidth = (m_CharWidth * 2); int hexGroupWidth = (byteWidth * m_NumBytesPerGroup) + (m_CharWidth * 1); int headerHeight = m_CharHeight; rc->left = addrColumnWidth + (nGroup * hexGroupWidth) + (byteOffs * byteWidth); rc->top = headerHeight + nRow * m_CharHeight; rc->right = rc->left + m_CharWidth * 2; rc->bottom = rc->top + m_CharHeight; } void CHexEditCtrl::GetAsciiCellPos(int index, CRect * rc) { int nRow = index / m_NumBytesPerRow; int rowOffs = (index % m_NumBytesPerRow); int addrColumnWidth = (m_CharWidth * 11); int byteWidth = (m_CharWidth * 2); int hexGroupWidth = (byteWidth * m_NumBytesPerGroup) + (m_CharWidth * 1); int hexColumnWidth = (m_NumByteGroupsPerRow * hexGroupWidth); int asciiColumnX = 0 + addrColumnWidth + hexColumnWidth; int headerHeight = m_CharHeight; rc->left = asciiColumnX + (rowOffs * m_CharWidth); rc->top = headerHeight + nRow * m_CharHeight; rc->right = rc->left + m_CharWidth; rc->bottom = rc->top + m_CharHeight; } char CHexEditCtrl::ByteAscii(uint8_t value) { // ISO 8859-1 if ((value >= 0x20 && value <= 0x7E) || value >= 0xA1) { return (char)value; } return '.'; } uint8_t CHexEditCtrl::HexCharValue(char c) { if (c >= '0' && c <= '9') return (c - '0'); if (c >= 'A' && c <= 'F') return (c - 'A') + 0x0A; if (c >= 'a' && c <= 'f') return (c - 'a') + 0x0A; return 0; } void CHexEditCtrl::CaretIncrementNibble(void) { if (!m_bCaretLoNibble) { m_bCaretLoNibble = true; } else { m_bCaretLoNibble = false; m_CaretAddress++; } } void CHexEditCtrl::CaretDecrementNibble(void) { if (m_bCaretLoNibble) { m_bCaretLoNibble = false; } else { m_bCaretLoNibble = true; m_CaretAddress--; } } void CHexEditCtrl::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent == TIMER_ID_AUTO_REFRESH) { Draw(); } else if (nIDEvent == TIMER_ID_DRAG_SCROLL) { if (m_DragScrollDelta != 0) { int numBytesToScroll = m_DragScrollDelta * m_NumBytesPerRow; int64_t newCaretAddress = (int64_t)m_CaretAddress + numBytesToScroll; if (newCaretAddress < 0 && m_BaseAddress == 0) { return; } else if (newCaretAddress > UINT_MAX) { return; } m_CaretAddress = SatAdd32(m_CaretAddress, numBytesToScroll); m_SelEndAddress = SatAdd32(m_SelEndAddress, numBytesToScroll); UpdateRealSelection(); UpdateCaretUI(true); } } } void CHexEditCtrl::OnPaint(CDCHandle dc) { PAINTSTRUCT ps; HDC hdc = BeginPaint(&ps); CRect rc = ps.rcPaint; BitBlt(hdc, rc.left, rc.top, rc.Width(), rc.Height(), m_BackDC, rc.left, rc.top, SRCCOPY); EndPaint(&ps); } void CHexEditCtrl::OnRButtonDown(UINT /*nFlags*/, CPoint point) { SetFocus(); HXHITTEST ht; HitTest(point.x, point.y, &ht); if (ht.column == HX_COL_HEXDATA) { if (!IsSelected(ht.hexAddress)) { m_CaretAddress = ht.hexAddress; m_bCaretLoNibble = HX_LEFT; CancelSelection(); } } else if (ht.column == HX_COL_ASCII) { if (!IsSelected(ht.asciiAddress)) { m_CaretAddress = ht.asciiAddress; CancelSelection(); } } } void CHexEditCtrl::OnRButtonUp(UINT /*nFlags*/, CPoint point) { HXHITTEST ht; HitTest(point.x, point.y, &ht); if (ht.column == HX_COL_HEXDATA) { NotifyRightClick(ht.hexAddress); } else if (ht.column == HX_COL_ASCII) { NotifyRightClick(ht.asciiAddress); } } void CHexEditCtrl::OnLButtonDown(UINT nFlags, CPoint point) { m_bLButtonDown = true; SetFocus(); HXHITTEST ht; HitTest(point.x, point.y, &ht); m_FocusedColumn = ht.column; if (m_FocusedColumn == HX_COL_HEXDATA) { m_CaretAddress = ht.hexAddress; m_bCaretLoNibble = (ht.hexCellSide == HX_RIGHT); if (nFlags & MK_SHIFT) { m_SelEndAddress = ht.hexAddress; m_SelEndCellSide = ht.hexCellSide; UpdateRealSelection(); if (GetSelDirection() > 0) { m_CaretAddress = m_RealSelEndAddress + 1; } else { m_CaretAddress = m_RealSelStartAddress; } m_bCaretLoNibble = false; } else { m_SelStartAddress = ht.hexAddress; m_SelEndAddress = ht.hexAddress; m_SelStartCellSide = ht.hexCellSide; m_SelEndCellSide = ht.hexCellSide; UpdateRealSelection(); } } else if (m_FocusedColumn == HX_COL_ASCII) { m_CaretAddress = ht.asciiAddress; if (nFlags & MK_SHIFT) { m_SelEndAddress = ht.asciiAddress; } else { m_CaretAddress = ht.asciiAddress; m_SelStartCellSide = ht.asciiCellSide; m_SelEndCellSide = ht.asciiCellSide; m_SelStartAddress = ht.asciiAddress; m_SelEndAddress = ht.asciiAddress; if (ht.asciiCellSide) { m_CaretAddress++; } } UpdateRealSelection(); } UpdateCaretUI(false); Draw(); SetCapture(); } void CHexEditCtrl::OnLButtonDblClk(UINT /*nFlags*/, CPoint point) { m_bDblClicked = true; HXHITTEST ht; HitTest(point.x, point.y, &ht); if (m_FocusedColumn == HX_COL_HEXDATA) { // Select word uint32_t offset = (ht.hexAddress - m_BaseAddress); uint32_t wordOffset = offset - (offset % m_NumBytesPerGroup); uint32_t wordAddress = m_BaseAddress + wordOffset; m_SelStartAddress = wordAddress; m_SelEndAddress = wordAddress + (m_NumBytesPerGroup - 1); m_SelStartCellSide = HX_LEFT; m_SelEndCellSide = HX_RIGHT; m_CaretAddress = m_SelEndAddress + 1; m_bCaretLoNibble = false; UpdateRealSelection(); UpdateCaretUI(false); } if (m_FocusedColumn == HX_COL_ASCII) { // Select row uint32_t offset = (ht.asciiAddress - m_BaseAddress); uint32_t rowOffset = (ht.asciiAddress - m_BaseAddress) - (offset % m_NumBytesPerRow); uint32_t rowAddress = m_BaseAddress + rowOffset; m_SelStartAddress = rowAddress; m_SelEndAddress = rowAddress + (m_NumBytesPerRow - 1); m_SelStartCellSide = HX_LEFT; m_SelEndCellSide = HX_RIGHT; m_CaretAddress = m_SelEndAddress + 1; UpdateRealSelection(); UpdateCaretUI(false); } } void CHexEditCtrl::OnLButtonUp(UINT /*nFlags*/, CPoint point) { m_bLButtonDown = false; m_bMouseDragging = false; if (m_DragScrollDelta != 0) { m_bDblClicked = false; m_DragScrollDelta = 0; ReleaseCapture(); return; } HXHITTEST ht; HitTest(point.x, point.y, &ht); if (m_bDblClicked) { m_bDblClicked = false; return; } ReleaseCapture(); } void CHexEditCtrl::OnMouseMove(UINT /*nFlags*/, CPoint point) { if (m_bLButtonDown) { m_bMouseDragging = true; } HXHITTEST ht; HitTest(point.x, point.y, &ht); if (ht.column == HX_COL_NONE || ht.column == HX_COL_ADDRESS) { m_bShowHotAddress = false; } else { m_bShowHotAddress = true; if (ht.column == HX_COL_HEXDATA) { if (m_HotAddress != ht.hexAddress) { m_HotAddress = ht.hexAddress; Notify(HXN_HOTADDRCHANGED); } } else if (ht.column == HX_COL_ASCII) { if (m_HotAddress != ht.asciiAddress) { m_HotAddress = ht.asciiAddress; Notify(HXN_HOTADDRCHANGED); } } } if (!m_bLButtonDown) { return; } m_DragScrollDelta = 0; if (point.y > m_HexDataColumnRect.bottom) { m_DragScrollDelta = 1 + (point.y - m_HexDataColumnRect.bottom) / m_CharHeight; } else if (point.y < m_HexDataColumnRect.top) { m_DragScrollDelta = -1 + (point.y - m_HexDataColumnRect.top) / m_CharHeight; } if (m_FocusedColumn == HX_COL_HEXDATA) { m_CaretAddress = ht.hexAddress; m_SelEndAddress = ht.hexAddress; m_SelEndCellSide = ht.hexCellSide; m_bCaretLoNibble = (ht.hexCellSide == HX_RIGHT); if (m_SelEndAddress - m_SelStartAddress == 1 && m_SelStartCellSide == HX_RIGHT && m_SelEndCellSide == HX_LEFT) { m_SelStartCellSide = HX_LEFT; } if (GetSelDirection() != 0 && m_SelEndCellSide == HX_RIGHT) { m_bCaretLoNibble = false; m_CaretAddress++; } } else if (m_FocusedColumn == HX_COL_ASCII) { m_CaretAddress = ht.asciiAddress; m_SelEndAddress = ht.asciiAddress; m_SelEndCellSide = ht.asciiCellSide; if (GetSelDirection() != 0 && m_SelEndCellSide == HX_RIGHT) { m_CaretAddress++; } } UpdateRealSelection(); } BOOL CHexEditCtrl::OnMouseWheel(UINT /*nFlags*/, short zDelta, CPoint /*pt*/) { m_BaseAddress = SatAdd32(m_BaseAddress, -(zDelta / 120) * m_NumBytesPerRow); Notify(HXN_BASEADDRCHANGED); return FALSE; } void CHexEditCtrl::OnSetFocus(CWindow /*wndOld*/) { ::CreateCaret(m_hWnd, nullptr, 2, m_CharHeight); m_bHaveCaret = true; UpdateCaretUI(false); } void CHexEditCtrl::OnKillFocus(CWindow /*wndFocus*/) { m_bCaretVisible = false; m_bHaveCaret = false; ::DestroyCaret(); } UINT CHexEditCtrl::OnGetDlgCode(LPMSG /*lpMsg*/) { return DLGC_WANTALLKEYS; } void CHexEditCtrl::OnChar(UINT nChar, UINT /*nRepCnt*/, UINT /*nFlags*/) { if (::GetKeyState(VK_CONTROL) & 0x8000) { return; } if (nChar == VK_BACK || nChar == VK_TAB || nChar == VK_RETURN) { return; } if (m_FocusedColumn == HX_COL_HEXDATA) { if (isxdigit(nChar)) { int selDirection = GetSelDirection(); if (selDirection < 0) { m_CaretAddress = m_SelEndAddress; m_bCaretLoNibble = false; } else if (selDirection > 0) { m_CaretAddress = m_SelStartAddress; m_bCaretLoNibble = false; } NotifySetNibble(m_CaretAddress, m_bCaretLoNibble, HexCharValue((char)nChar)); CancelSelection(); CaretIncrementNibble(); UpdateCaretUI(true); } } else if (m_FocusedColumn == HX_COL_ASCII) { int selDirection = GetSelDirection(); if (selDirection < 0) { m_CaretAddress = m_SelEndAddress; } else if (selDirection > 0) { m_CaretAddress = m_SelStartAddress; } NotifySetByte(m_CaretAddress, (uint8_t)nChar); CancelSelection(); m_CaretAddress++; UpdateCaretUI(true); } } void CHexEditCtrl::Paste(bool bAdvanceCaret) { uint32_t targetAddress = m_bHaveRealSel ? m_RealSelStartAddress : m_CaretAddress; int retLength = (int)((INT_PTR)NotifyPaste(targetAddress)); if (retLength != 0) { if (bAdvanceCaret) { m_CaretAddress = targetAddress + retLength; UpdateCaretUI(true); } CancelSelection(); } } void CHexEditCtrl::Copy(void) { if (m_bHaveRealSel) { Notify(HXN_COPY); } } void CHexEditCtrl::SetBaseAddress(uint32_t address) { if (m_BaseAddress != address) { m_BaseAddress = address; Draw(); } } uint32_t CHexEditCtrl::GetCaretAddress(void) { return m_CaretAddress; } uint32_t CHexEditCtrl::GetHotAddress(void) { return m_HotAddress; } uint32_t CHexEditCtrl::GetBaseAddress(void) { return m_BaseAddress; } int CHexEditCtrl::GetNumBytesPerRow(void) { return m_NumBytesPerRow; } int CHexEditCtrl::GetNumVisibleBytes(void) { return m_NumVisibleBytes; } int CHexEditCtrl::GetNumBytesPerGroup(void) { return m_NumBytesPerGroup; } bool CHexEditCtrl::GetSelectionRange(uint32_t * startAddress, uint32_t * endAddress) { *startAddress = m_RealSelStartAddress; *endAddress = m_RealSelEndAddress; return m_bHaveRealSel; } bool CHexEditCtrl::GetInsertMode(void) { return m_bInsertMode; } HXCOLUMN CHexEditCtrl::GetFocusedColumn(void) { return m_FocusedColumn; } void CHexEditCtrl::OnKeyDown(UINT nChar, UINT /*nRepCnt*/, UINT /*nFlags*/) { if (nChar != VK_CONTROL && (GetKeyState(VK_CONTROL) & 0x8000)) { NotifyCtrlKeyPressed(nChar); if (nChar == 'V') { Paste(); } else if (nChar == 'B') { Paste(false); } else if (nChar == 'C') { Copy(); } else if (nChar == 'X') { Copy(); if (m_bHaveRealSel) { NotifyFillRange(m_RealSelStartAddress, m_RealSelEndAddress, 0); m_CaretAddress = m_RealSelStartAddress; m_bCaretLoNibble = false; CancelSelection(); } } else if (nChar == 'A') { SelectAllVisible(); } } if (nChar == VK_DOWN) { m_CaretAddress = SatAdd32(m_CaretAddress, m_NumBytesPerRow); if (GetKeyState(VK_SHIFT) & 0x8000) { m_SelEndAddress += m_NumBytesPerRow; if (m_bCaretLoNibble) { m_SelStartCellSide = HX_LEFT; m_SelEndCellSide = HX_LEFT; m_bCaretLoNibble = false; } UpdateRealSelection(); } else { CancelSelection(); } UpdateCaretUI(true); } else if (nChar == VK_UP) { m_CaretAddress -= m_NumBytesPerRow; if (GetKeyState(VK_SHIFT) & 0x8000) { m_SelEndAddress -= m_NumBytesPerRow; if (m_bCaretLoNibble) { m_SelStartCellSide = HX_LEFT; m_SelEndCellSide = HX_LEFT; m_bCaretLoNibble = false; } UpdateRealSelection(); } else { CancelSelection(); } UpdateCaretUI(true); } else if (nChar == VK_RIGHT) { if (m_FocusedColumn == HX_COL_HEXDATA) { if (GetKeyState(VK_SHIFT) & 0x8000) { if (m_SelStartAddress == m_SelEndAddress && m_SelStartCellSide == HX_RIGHT && m_SelEndCellSide == HX_RIGHT) { m_SelStartCellSide = HX_LEFT; m_SelEndCellSide = HX_RIGHT; } else { m_SelEndAddress++; } m_bCaretLoNibble = false; m_CaretAddress++; UpdateRealSelection(); } else if (GetKeyState(VK_CONTROL) & 0x8000) { CaretIncrementNibble(); CancelSelection(); } else { m_bCaretLoNibble = false; m_CaretAddress++; m_SelStartAddress = m_CaretAddress; m_SelEndAddress = m_CaretAddress; m_SelStartCellSide = HX_LEFT; m_SelEndCellSide = HX_LEFT; CancelSelection(); } } else if (m_FocusedColumn == HX_COL_ASCII) { m_CaretAddress++; if (GetKeyState(VK_SHIFT)) { m_SelEndCellSide = HX_LEFT; m_SelEndAddress = m_CaretAddress; UpdateRealSelection(); } else { CancelSelection(); } } UpdateCaretUI(true); } else if (nChar == VK_LEFT) { if (m_FocusedColumn == HX_COL_HEXDATA) { if (GetKeyState(VK_SHIFT) & 0x8000) { m_SelEndCellSide = HX_LEFT; m_SelEndAddress--; if (m_bCaretLoNibble) { m_SelStartCellSide = HX_LEFT; } m_CaretAddress--; m_bCaretLoNibble = false; UpdateRealSelection(); } else if (GetKeyState(VK_CONTROL) & 0x8000) { CaretDecrementNibble(); CancelSelection(); } else { if (m_bCaretLoNibble) { CaretDecrementNibble(); } else { m_CaretAddress--; } CancelSelection(); } } else if (m_FocusedColumn == HX_COL_ASCII) { m_CaretAddress--; if (GetKeyState(VK_SHIFT)) { m_SelEndCellSide = HX_LEFT; m_SelEndAddress = m_CaretAddress; UpdateRealSelection(); } else { CancelSelection(); } } UpdateCaretUI(true); } else if (nChar == VK_NEXT || nChar == VK_PRIOR) // Page down, page up { int delta = (nChar == VK_NEXT) ? m_NumVisibleBytes : -m_NumVisibleBytes; if (IsCaretAddressVisible()) { m_BaseAddress += delta; m_CaretAddress += delta; Notify(HXN_BASEADDRCHANGED); } else { m_CaretAddress += delta; UpdateCaretUI(true, true); } CancelSelection(); } else if (nChar == VK_INSERT) { m_bInsertMode = !m_bInsertMode; Notify(HXN_INSERTMODECHANGED); } else if (nChar == VK_RETURN) { Notify(HXN_ENTERPRESSED); } else if (nChar == VK_HOME) { UpdateCaretUI(true); } else if (nChar == VK_BACK) { if (m_bHaveRealSel) { NotifyFillRange(m_RealSelStartAddress, m_RealSelEndAddress, 0); CancelSelection(); m_CaretAddress = m_RealSelStartAddress; UpdateCaretUI(true); } else { if (m_FocusedColumn == HX_COL_HEXDATA) { uint32_t address = m_CaretAddress + (m_bCaretLoNibble ? 0 : -1); NotifySetNibble(address, !m_bCaretLoNibble, 0); CaretDecrementNibble(); UpdateCaretUI(true); } else if (m_FocusedColumn == HX_COL_ASCII) { NotifySetByte(m_CaretAddress - 1, 0); m_CaretAddress--; UpdateCaretUI(true); } } } else if (nChar == VK_DELETE) { if (m_FocusedColumn == HX_COL_HEXDATA || m_FocusedColumn == HX_COL_ASCII) { if (!m_bHaveRealSel) { NotifySetByte(m_CaretAddress, 0); } else { NotifyFillRange(m_RealSelStartAddress, m_RealSelEndAddress, 0); } } } } int CHexEditCtrl::GetSelDirection(void) { if (m_SelStartAddress < m_SelEndAddress) return 1; // Right if (m_SelStartAddress > m_SelEndAddress) return -1; // Left if (m_SelStartCellSide == m_SelEndCellSide) return 0; // No selection if (m_SelStartCellSide == HX_LEFT && m_SelEndCellSide == HX_RIGHT) return 1; // Right (single byte) if (m_SelStartCellSide == HX_RIGHT && m_SelEndCellSide == HX_LEFT) return -1; // Left (single byte) return 0; } void CHexEditCtrl::CancelSelection(void) { m_SelStartAddress = m_CaretAddress; m_SelEndAddress = m_CaretAddress; m_SelStartCellSide = m_bCaretLoNibble ? HX_RIGHT : HX_LEFT; m_SelEndCellSide = m_bCaretLoNibble ? HX_RIGHT : HX_LEFT; UpdateRealSelection(); } void CHexEditCtrl::SelectAllVisible(void) { uint32_t lastVisibleByteAddress = (m_BaseAddress + m_NumVisibleBytes) - 1; m_SelStartAddress = m_BaseAddress; m_SelStartCellSide = HX_LEFT; m_SelEndAddress = lastVisibleByteAddress; m_SelEndCellSide = HX_RIGHT; m_CaretAddress = lastVisibleByteAddress + 1; m_bCaretLoNibble = false; UpdateRealSelection(); } bool CHexEditCtrl::IsCaretAddressVisible(void) { return m_CaretAddress >= m_BaseAddress && m_CaretAddress <= SatAdd32(m_BaseAddress, m_NumVisibleBytes); } uint32_t CHexEditCtrl::LineAddress(uint32_t address) { return address - ((address - (m_BaseAddress % m_NumBytesPerRow)) % m_NumBytesPerRow); } void CHexEditCtrl::EnsureCaretAddressVisible(bool bTop) { uint32_t oldBaseAddress = m_BaseAddress; uint32_t caretLineAddress = LineAddress(m_CaretAddress); uint32_t lastVisibleLineAddress = m_BaseAddress + (m_NumVisibleBytes - m_NumBytesPerRow); if (bTop || caretLineAddress < m_BaseAddress) { m_BaseAddress = caretLineAddress; } else if (caretLineAddress >= lastVisibleLineAddress + m_NumBytesPerRow) { m_BaseAddress = SatAdd32(m_BaseAddress, caretLineAddress - lastVisibleLineAddress); } if (oldBaseAddress != m_BaseAddress) { Notify(HXN_BASEADDRCHANGED); } } LRESULT CHexEditCtrl::Notify(UINT code) { UINT_PTR nID = ::GetDlgCtrlID(m_hWnd); NMHDR nmh = {m_hWnd, nID, code}; return ::SendMessage(GetParent(), WM_NOTIFY, (WPARAM)nID, (LPARAM)&nmh); } LRESULT CHexEditCtrl::NotifySetByte(uint32_t address, uint8_t value) { UINT_PTR nID = ::GetDlgCtrlID(m_hWnd); NMHXSETBYTE nmsb = {{m_hWnd, nID, HXN_SETBYTE}, m_bInsertMode, address, value}; return ::SendMessage(GetParent(), WM_NOTIFY, (WPARAM)nID, (LPARAM)&nmsb); } LRESULT CHexEditCtrl::NotifySetNibble(uint32_t address, bool bLoNibble, uint8_t value) { UINT_PTR nID = ::GetDlgCtrlID(m_hWnd); NMHXSETNIBBLE nmsn = {{m_hWnd, nID, HXN_SETNIBBLE}, m_bInsertMode, address, bLoNibble, value}; return ::SendMessage(GetParent(), WM_NOTIFY, (WPARAM)nID, (LPARAM)&nmsn); } LRESULT CHexEditCtrl::NotifyFillRange(uint32_t startAddress, uint32_t endAddress, uint8_t value) { UINT_PTR nID = ::GetDlgCtrlID(m_hWnd); NMHXFILLRANGE nmfr = {{m_hWnd, nID, HXN_FILLRANGE}, m_bInsertMode, startAddress, endAddress, value}; return ::SendMessage(GetParent(), WM_NOTIFY, (WPARAM)nID, (LPARAM)&nmfr); } LRESULT CHexEditCtrl::NotifyCtrlKeyPressed(int nChar) { UINT_PTR nID = ::GetDlgCtrlID(m_hWnd); NMHXCTRLKEYPRESSED nmck = {{m_hWnd, nID, HXN_CTRLKEYPRESSED}, nChar}; return ::SendMessage(GetParent(), WM_NOTIFY, (WPARAM)nID, (LPARAM)&nmck); } LRESULT CHexEditCtrl::NotifyPaste(uint32_t address) { UINT_PTR nID = ::GetDlgCtrlID(m_hWnd); NMHXPASTE nmp = {{m_hWnd, nID, HXN_PASTE}, address, m_FocusedColumn}; return ::SendMessage(GetParent(), WM_NOTIFY, (WPARAM)nID, (LPARAM)&nmp); } LRESULT CHexEditCtrl::NotifyRightClick(uint32_t address) { UINT_PTR nID = ::GetDlgCtrlID(m_hWnd); NMHXRCLICK nmrc = {{m_hWnd, nID, HXN_RCLICK}, address}; return ::SendMessage(GetParent(), WM_NOTIFY, (WPARAM)nID, (LPARAM)&nmrc); } LRESULT CHexEditCtrl::NotifyGetByteInfo(uint32_t address, size_t numBytes, bool bIgnoreDiff, HXBYTEINFO * oldBytes, HXBYTEINFO * newBytes) { UINT_PTR nID = ::GetDlgCtrlID(m_hWnd); NMHXGETBYTEINFO nmgbi = {{m_hWnd, nID, HXN_GETBYTEINFO}, address, numBytes, bIgnoreDiff, oldBytes, newBytes}; return ::SendMessage(GetParent(), WM_NOTIFY, nmgbi.nmh.idFrom, (LPARAM)&nmgbi); } BOOL CHexEditCtrl::OnSetCursor(CWindow /*wnd*/, UINT /*nHitTest*/, UINT /*message*/) { CPoint point(::GetMessagePos()); ScreenToClient(&point); HXHITTEST ht; HitTest(point.x, point.y, &ht); if (ht.column == HX_COL_HEXDATA || ht.column == HX_COL_ASCII) { SetCursor(m_hCursorIBeam); } else { SetCursor(m_hCursorDefault); } return FALSE; } void CHexEditCtrl::ShowCaret(void) { if (!m_bCaretVisible) { ::ShowCaret(m_hWnd); m_bCaretVisible = true; } } void CHexEditCtrl::HideCaret(void) { if (m_bCaretVisible) { ::HideCaret(m_hWnd); m_bCaretVisible = false; } } uint32_t CHexEditCtrl::SatAdd32(uint32_t a, int b) { int64_t c = (int64_t)a + b; if (c > UINT_MAX) { return UINT_MAX; } if (c < 0) { return 0; } return (uint32_t)c; } uint32_t CHexEditCtrl::SatAdd32(uint32_t a, uint32_t b) { uint32_t c = a + b; if (c < a) { return (uint32_t)-1; } return c; } COLORREF CHexEditCtrl::BlendColor(COLORREF c1, COLORREF c2) { int r1 = GetRValue(c1); int g1 = GetGValue(c1); int b1 = GetBValue(c1); int r2 = GetRValue(c2); int g2 = GetGValue(c2); int b2 = GetBValue(c2); return RGB((r1 + r2 * 2) / 3, (g1 + g2 * 2) / 3, (b1 + b2 * 2) / 3); } void CHexEditCtrl::UpdateLayoutInfo(void) { CRect clientRect; GetClientRect(&clientRect); int addressColumnWidth = 11 * m_CharWidth; int byteWidth = m_CharWidth * 2; int byteGroupWidth = (m_NumBytesPerGroup * byteWidth) + (m_CharWidth * 1); int asciiGroupWidth = (m_NumBytesPerGroup * m_CharWidth); int headerHeight = m_CharHeight; m_NumByteGroupsPerRow = (clientRect.Width() - addressColumnWidth) / (byteGroupWidth + asciiGroupWidth); m_NumBytesPerRow = m_NumByteGroupsPerRow * m_NumBytesPerGroup; m_NumVisibleRows = (clientRect.Height() - headerHeight) / m_CharHeight; m_NumVisibleBytes = m_NumVisibleRows * m_NumBytesPerRow; int hexDataColumnWidth = m_NumByteGroupsPerRow * byteGroupWidth; int asciiColumnWidth = m_NumBytesPerRow * m_CharWidth; int addressColumnLeft = 0; int addressColumnRight = addressColumnLeft + addressColumnWidth; int hexDataColumnLeft = addressColumnRight; int hexDataColumnRight = hexDataColumnLeft + hexDataColumnWidth; int asciiColumnLeft = hexDataColumnRight; int asciiColumnRight = asciiColumnLeft + asciiColumnWidth; int columnsTop = 0 + headerHeight; int columnsBottom = columnsTop + m_NumVisibleRows * m_CharHeight; m_AddressColumnRect = {addressColumnLeft, columnsTop, addressColumnRight, columnsBottom}; m_HexDataColumnRect = {hexDataColumnLeft, columnsTop, hexDataColumnRight, columnsBottom}; m_AsciiColumnRect = {asciiColumnLeft, columnsTop, asciiColumnRight, columnsBottom}; m_bLayoutChanged = true; } void CHexEditCtrl::ReallocByteBuffers(void) { m_NewBytes = (HXBYTEINFO *)realloc(m_NewBytes, m_NumVisibleBytes * sizeof(HXBYTEINFO)); m_OldBytes = (HXBYTEINFO *)realloc(m_OldBytes, m_NumVisibleBytes * sizeof(HXBYTEINFO)); for (int i = 0; i < m_NumVisibleBytes; i++) { m_NewBytes[i] = {0}; m_OldBytes[i] = {0}; } } void CHexEditCtrl::OnWindowPosChanged(LPWINDOWPOS /*lpWndPos*/) { int oldNumRows = m_NumVisibleRows; int oldNumGroups = m_NumByteGroupsPerRow; UpdateLayoutInfo(); if (oldNumRows != m_NumVisibleRows || oldNumGroups != m_NumByteGroupsPerRow) { ReallocByteBuffers(); CRect rc; GetClientRect(&rc); m_BackBMP = CreateCompatibleBitmap(m_BackDC, rc.Width(), rc.Height()); HBITMAP hOldBMP = (HBITMAP)SelectObject(m_BackDC, m_BackBMP); DeleteObject(hOldBMP); CRect clrRc(0, 0, rc.Width(), rc.Height()); HBRUSH hbrush = CreateSolidBrush(BKCOLOR_DEFAULT); FillRect(m_BackDC, clrRc, hbrush); DeleteObject(hbrush); Draw(); DrawAddressColumn(); DrawHeader(); } } void CHexEditCtrl::SetByteGroupSize(int nBytes) { m_NumBytesPerGroup = nBytes; Notify(HXN_GROUPSIZECHANGED); UpdateLayoutInfo(); ReallocByteBuffers(); CRect rc; GetClientRect(&rc); CRect clrRc(0, 0, rc.Width(), rc.Height()); HBRUSH hbrush = CreateSolidBrush(BKCOLOR_DEFAULT); FillRect(m_BackDC, clrRc, hbrush); DeleteObject(hbrush); Draw(); DrawAddressColumn(); DrawHeader(); int addressColumnWidth = 11 * m_CharWidth; int headerHeight = m_CharHeight; CRect rcInv = {addressColumnWidth, headerHeight, rc.Width(), rc.Height()}; InvalidateRect(&rcInv, true); }