You can do it with a bit of hackery. I have managed to do it. Since the ribbon draws the caption in its client area itself its possible to manually position a control up there yourself by overriding CalcDockingLayout. Similar to how the quick access toolbar buttons or minimize, maximize, close caption buttons are positioned.
One challenge of positioning an edit control in the caption title though is that context tab captions and caption text can get in your way. You kind of are forced to dodge your edit control around them. For that reason I would recommend just keeping the edit control in the tab area. You can then actually left align to the tabs.
Here is some code that you might be able to adapt. Use at your own peril :)
// MmRibbonBar.h
/// /// \enum eSearchPosition /// enum eSearchPosition { eSearchPositionTabControl = 0, ///< Next to the tab control, similar to Office 2016 products. eSearchPositionTitleBar, ///< In the frame hook title bar, similar to Office 365 (late 2019). eSearchPositionDefault = eSearchPositionTabControl };
#define SEARCH_MINIMUM_WIDTH XPT_DPI_X(40) #define SEARCH_MAXIMUM_WIDTH XTP_DPI_X(220) #define SEARCH_HEIGHT XTP_DPI_Y(22)
...
// MmRibbonBar.cpp
CSize CMmRibbonBar::CalcDockingLayout(int nLength, DWORD dwMode, int nWidth /*= 0*/) { CXTPControl *pControlSearch = NULL; if (dwMode & LM_COMMIT) { pControlSearch = m_pControls->FindControl(xtpControlEdit, ID_COMMAND_SEARCH, TRUE, FALSE); if (pControlSearch) { if (m_searchPosition == eSearchPositionTabControl) { pControlSearch->SetVisible(TRUE); // Minimum width and padding. pControlSearch->SetWidth(SEARCH_MINIMUM_WIDTH + XTP_DPI_X(16)); } else { // Hide before base calc docking layout to avoid this control effecting the size. pControlSearch->SetVisible(FALSE); } } }
const CSize sz = CXTPRibbonBar::CalcDockingLayout(nLength, dwMode, nWidth);
if (dwMode & LM_COMMIT) { if (pControlSearch) { // Left align the left-most right aligned edit control to the last visible tab item. CRect rcSearch = GetSearchControlRect(pControlSearch);
if (IsBackstageViewVisible()) { // Hide when backstage view is active. pControlSearch->SetVisible(FALSE); } else if (m_searchPosition == eSearchPositionTabControl) { ASSERT(pControlSearch->IsVisible()); pControlSearch->SetWidth(rcSearch.Width()); pControlSearch->SetRect(rcSearch);
if (m_pControlTab && m_rcTabControl.right > rcSearch.left) { // Resize tab control. // Don't want overlap between the edit control and the tab control as it messes with hit testing. m_rcTabControl.right = rcSearch.left; m_pControlTab->SetRect(m_rcTabControl); } } else { // If there is not enough room, hide the control. if (rcSearch.Width() >= SEARCH_MINIMUM_WIDTH) { pControlSearch->SetRect(rcSearch); pControlSearch->SetVisible(TRUE); } } } }
return sz; } CRect CMmRibbonBar::GetSearchControlRect(CXTPControl *pControlSearch) const { using std::min; using std::max;
CRect rcControl;
switch (m_searchPosition) { case eSearchPositionTabControl: { // Get last visible tab item. const CXTPTabManagerItem *pLastTabItem = NULL; for (int i = m_pControlTab->GetItemCount() - 1; i >= 0; --i) { const CXTPTabManagerItem *pTabItem = m_pControlTab->GetItem(i); if (pTabItem->IsVisible()) { pLastTabItem = pTabItem; break; } }
// Get next visible ribbon background control to the right of this control. CXTPControl *pRHSControl = NULL; for (int i = pControlSearch->GetIndex() + 1; i < GetControlCount(); ++i) { CXTPControl *pControl = GetControl(i); if (pControl && pControl->IsVisible() && pControl->GetFlags() & (xtpFlagRightAlign | xtpFlagRibbonTabBackground) && pControl->GetID() != ID_COMMAND_SEARCH) { pRHSControl = pControl; break; } }
if (pLastTabItem) { const CRect rcLastTab = pLastTabItem->GetRect();
CRect rcClient; GetClientRect(&rcClient);
int nLeft = rcLastTab.right + XTP_DPI_X(8); int nTop = rcLastTab.top; int nRight = rcClient.right - XTP_DPI_X(8); if (pRHSControl) nRight = pRHSControl->GetRect().left - XTP_DPI_X(8); int nBottom = rcLastTab.bottom - XTP_DPI_Y(2);
int nSearchMinWidth = SEARCH_MINIMUM_WIDTH; int nSearchMaxWidth = SEARCH_MAXIMUM_WIDTH;
int nSearchWidth = max(nSearchMinWidth, min(nSearchMaxWidth, nRight - nLeft)); int nSearchHeight = SEARCH_HEIGHT;
// Left align the left-most right aligned edit control to the last visible tab item. rcControl = CRect(nLeft, nTop + (nBottom - nTop) / 2 - nSearchHeight / 2, nLeft + nSearchWidth, nTop + (nBottom - nTop) / 2 + nSearchHeight / 2); } } break; case eSearchPositionTitleBar: { // Get frame hook caption button. CXTPControl *pMinimize = m_pControls->FindControl(SC_MINIMIZE);
// Get last visible tab item. const CXTPRibbonTabContextHeader *pFirstContextHeader = NULL; const CXTPRibbonTabContextHeader *pLastContextHeader = NULL; for (int i = m_pControlTab->GetItemCount() - 1; i >= 0; --i) { CXTPRibbonTab *pTab = DYNAMIC_DOWNCAST(CXTPRibbonTab, m_pControlTab->GetItem(i)); if (pTab && pTab->IsVisible()) { CXTPRibbonTabContextHeader *pContextHeader = pTab->GetContextHeader(); if (pContextHeader) { if (!pLastContextHeader) pLastContextHeader = pContextHeader; pFirstContextHeader = pContextHeader; } } }
if (pMinimize) { CRect rcMinimize = pMinimize->GetRect(); CRect rcCaption = m_rcCaptionText;
int nMaxSearchWidth = SEARCH_MAXIMUM_WIDTH;
int nTop = rcMinimize.top + XTP_DPI_Y(4); int nBottom = rcMinimize.bottom - XTP_DPI_Y(4); int nLeft, nRight; if (pFirstContextHeader && pLastContextHeader) { // pFirstContextHeader can be equal to pLastContextHeader if only a single context is up. CRect rcFirstContextTab = pFirstContextHeader->m_rcRect; CRect rcLastContextTab = pLastContextHeader->m_rcRect;
// Left side of context titles. int nLeftLeft = rcCaption.right + XTP_DPI_X(8); int nLeftRight = rcFirstContextTab.left - XTP_DPI_X(8);
// Right side of context titles (preferred). int nRightLeft = std::max(rcCaption.right, rcLastContextTab.right) + XTP_DPI_X(8); int nRightRight = rcMinimize.left - XTP_DPI_X(8);
if (nLeftRight - nLeftLeft > nRightRight - nRightLeft && nRightRight - nRightLeft < nMaxSearchWidth) // Prefer right if it can fit max width. { nLeft = nLeftLeft; nRight = nLeftRight; } else { nLeft = nRightLeft; nRight = nRightRight; } } else { nLeft = rcCaption.right + XTP_DPI_X(8); nRight = rcMinimize.left - XTP_DPI_X(8); }
int nSearchWidth = min(nMaxSearchWidth, nRight - nLeft); int nSearchHeight = SEARCH_HEIGHT;
// Right align to the left-most caption button (minimize button). rcControl = CRect(nLeft + (nRight - nLeft) / 2 - nSearchWidth / 2, nTop + (nBottom - nTop) / 2 - nSearchHeight / 2, nLeft + (nRight - nLeft) / 2 + nSearchWidth / 2, nTop + (nBottom - nTop) / 2 + nSearchHeight / 2); } } break; default: ASSERT(0); }
return rcControl; }
|
In our ribbon resource, the ID_COMMAND_SEARCH control is an edit control on the defined on the tab background. This code then finds that control and manually repositions it. There is a minimum and maximum width defined for the search box to allow it to scale a bit for smaller screens. We do have some custom drawing as well to make it fit in the Office 2016 style if that theme is used.