Manually drawing gradients for buttons, toolbars, tabs etc? - c++

I would like to update some toolbar-like code we have to have a Vista/Win7 gradient roundedness to them.
Currently, the buttons have the Windows 2000 look & feel: blocky, single-tone.
I've played around with the XP themes, and using DrawThemeBackground, DrawThemeEdge, etc.; but I'm very dissatisfied with the theme drawing mechanics (the buttons are large, and the theme draws them as 2-tone, top half and bottom half, which looks okay when the buttons are small - it gives them a halfway decent appearance of being a gradient or having a rounded quality to them. But as large as these buttons are, they look stupid.
Experimenting by simply observing how many of the controls are drawn in various apps and controls, I can see that most of them seem to use gradients - where the top of the control appears a light color and fades to the bottom to a darker color - OR - where it is a lighter color than the background at the top, increases towards near-white at the middle, then fades back to a darker color towards the bottom.
I'm not really sure where to go from here. DrawThemeXXX seem to be inadequate. I don't really want to replace the entire control with a new one that has improved drawing but would require that I swap out some of the code for how the current custom control works, and risk various problems with some other library. I'd rather just have a way to draw arbitrary objects in the style of the current version of Windows that I'm running on. I would have thought that the theme drawing functions would have covered this. But they're fairly brain damaged, as I described.
Can someone point me towards 'How are modern C++ applications supposed to draw custom GUI elements so that they might reasonably expect a graceful appearance under XP, Vista, and Windows 7?'
We use MFC, Gdiplus, and raw Win32 APIs in our code, currently.
Here's to hoping someone knows a great deal about drawing modern GUIs under Windows from C++!
Just so that this isn't a wall of text, here's the current version of the paint handler, which draws the button with an etched border when 'hot-tracking' and both an etched border and the icon + text "depressed" (shifted by 1,1) when in a pressed state:
void CPlacesButton::PaintButton(CDC & dc, CRect & rcClient)
{
const int kLabelHeight = 8;
COLORREF clrHiLt = GetSysColor(COLOR_BTNHIGHLIGHT);
COLORREF clrShdo = GetSysColor(COLOR_BTNSHADOW);
COLORREF clrText = GetSysColor(COLOR_BTNTEXT);
COLORREF clrFace = GetSysColor(COLOR_BTNFACE);
// draw the button's background & border
if (m_bPressed || m_bDrawPressed || m_bMouseOnButton)
{
COLORREF clrDarkened = Darken(clrFace, -0.01f);
dc.FillRect(rcClient, &CBrush(clrDarkened));
//dc.Draw3dRect(rcClient, clrShdo, clrHiLt);
//dc.RoundRect(&rcClient, CPoint(10,10));
dc.DrawEdge(&rcClient, EDGE_ETCHED, BF_RECT|BF_FLAT);
//dc.DrawFrameControl(&rcClient, DFC_BUTTON, DFCS_BUTTONPUSH|DFCS_PUSHED);
}
// else if (m_bMouseOnButton) // hot draw
// //dc.Draw3dRect(rcClient, clrShdo, clrHiLt);
// dc.DrawEdge(&rcClient, EDGE_ETCHED, BF_RECT);
// //dc.RoundRect(&rcClient, CPoint(10,10));
else
dc.FillRect(rcClient, &CBrush(clrFace));
// use transparent mode for everything that follows
dc.SetBkMode(TRANSPARENT);
// center icon
CPoint ptIcon((rcClient.Width() - m_nIconSize) / 2, ((rcClient.Height() - m_nIconSize) / 2) - kLabelHeight);
if (m_bPressed || m_bDrawPressed)
ptIcon.Offset(1, 1);
// determine the state to draw ourselves in
const UINT nState = DST_ICON | (IsEnabled() ? DSS_NORMAL : DSS_DISABLED);
// draw our icon
dc.DrawState(ptIcon, CSize(m_nIconSize, m_nIconSize), m_hIcon, nState, (HBRUSH)NULL);
// create & select the font to use for the button's label
CFont guiFont;
VERIFY(guiFont.CreateStockObject(DEFAULT_GUI_FONT));
AutoSelectGDIObject select_font(dc, guiFont);
// determine clipping rect for label
CRect rcText(0, ptIcon.y+m_nIconSize+kLabelHeight, rcClient.Width(), ptIcon.y+m_nIconSize+kLabelHeight);
rcText.InflateRect(0,20);
if (m_bPressed || m_bDrawPressed)
rcText.OffsetRect(1, 1);
dc.SetTextColor(clrText);
if (IsEnabled())
dc.DrawText(m_strCaption, rcText, DT_VCENTER|DT_SINGLELINE|DT_CENTER);
else
dc.GrayString(NULL, NULL, (LPARAM)(LPCTSTR)m_strCaption, 0, rcText.TopLeft().x, rcText.TopLeft().y, rcText.Width(), rcText.Height());
}
I left some of the commented out variations in the code to indicate some hints as to what other possibilities I've tried. However, they're just a hint, as the complete alternate examples are not present.

Actually duplicating the look of the various flavors of Windows is ridiculously difficult, especially if your app can run on more than one version of windows.
I think that they intended to give you the api's to do this back in the Win2k/Win95 days, but then WinXP came along with shading and overlays, and the old API was completely inadequate.
So they came up with the theme stuff, which isn't really even an API so much as an API and a set of graphical primitives all jammed together. But they didn't follow through and allow the set of graphical primitives to be extended or replaced, so themes only works when your controls are a close match to the standard set.
So, for Win9x/Win2k. You use
DrawFrameControl
DrawEdge
For WinXP
DrawTheme
For WinVista/7
DrawTheme
DwmXXX functions
GradientFill ??
Now I suspect that Windows isn't actually using GradientDraw. I suspect it's actually using some DX10 shaders that are built in to the window manager code, but I don't know how to get at that, s I've been using GradientDraw instead. This code will give you a linear fade from the top of the control to the bottom.
INLINE void SetTrivertex(TRIVERTEX & vtx, int x, int y, COLORREF cr)
{
vtx.x = x;
vtx.y = y;
vtx.Red = (SHORT)(GetRValue(cr) * 256L);
vtx.Green = (SHORT)(GetGValue(cr) * 256L);
vtx.Blue = (SHORT)(GetBValue(cr) * 256L);
vtx.Alpha = (SHORT)(255 * 256L);
}
...
// fill the interior from the top down with a gradient that starts at crTop
// and ends with the crBottom
TRIVERTEX vtx[2];
SetTrivertex (vtx[0], prc->left+1, prc->top+1, crTop);
SetTrivertex (vtx[1], prc->right-1, prc->bottom-1, crBottom);
GRADIENT_RECT gRect = { 0, 1 };
GradientFill(hdc, vtx, 2, &gRect, 1, GRADIENT_FILL_RECT_V);

You never mentioned the MFC Feature Pack. Have you taken a look at it yet? Download for VS2008, included with VS2008 SP1. The CDrawingManager has lots of special effects. It has great support for application themes.

MFC alone isn't exactly skinning friendly. Apart from using another GUI (Qt is great for custom skinning) you can look at solutions like SkinCrafter.

Related

Setting title bar and border colors programmatically

I am trying to change my application' s title bar and border colors programmatically. I tried lots of things but with no success, and decided to change these colors system-wide. Because it is also acceptable for me to change title bar and border colors as my application is running, and revert them back in the end of my application. (Managed environment, with small set of applications running)
Is it possible to change these colors dynamically(process-wide, or system-wide unless process-wide change is possible)? Can you suggest any way to achieve this?
I tried something like the following but it doesn' t do what I want:
int aElements[2] = {COLOR_WINDOW, COLOR_ACTIVECAPTION};
DWORD aOldColors[2];
DWORD aNewColors[2];
aOldColors[0] = GetSysColor(aElements[0]);
aOldColors[1] = GetSysColor(aElements[1]);
aNewColors[0] = RGB(0x80, 0x80, 0x80); // light gray
aNewColors[1] = RGB(0x80, 0x00, 0x80); // dark purple
SetSysColors(2, aElements, aNewColors);
SetSysColors(2, aElements, aOldColors);
Thanks in advance
EDIT
This is exactly what I want:
I don't recommend to customize border and title redrawing.
It's really hard to do it the right way. Office just draws everything by itself in the client area but using normal border.
Using NC_PAINT the right way is a pain and may introduce flickering.
Especially positioning the minimize,maximize and close buttons is difficult, because every windows does it differently.
Also take into account accessibility, larger fonts used, customized user settings.
Whats the purpose of changing the colors?
To change the global colors you have to at least separate your code
// call this once at startup of your application (e.g. in WM_CREATE)
SetSysColors(2, aElements, aNewColors);
// call this when closing you application (e.g. in WM_DESTROY)
SetSysColors(2, aElements, aOldColors);
I know you are using C++, but I am handy with C#. So that you may get some idea, take look at the following code, which modifies form appearance.
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("User32.dll")]
private static extern IntPtr GetWindowDC(IntPtr hWnd);
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
const int WM_NCPAINT = 0x85;
if (m.Msg == WM_NCPAINT)
{
IntPtr hdc = GetWindowDC(m.HWnd);
if ((int)hdc != 0)
{
Graphics g = Graphics.FromHdc(hdc);
g.FillRectangle(Brushes.Green, new Rectangle(0, 0, 4800, 23));
g.Flush();
ReleaseDC(m.HWnd, hdc);
}
}
}
Also, you could use the Drawing Custom Borders in Windows Forms project from CodePlex. This project is a tiny library that allows users to customize Windows Forms, like customizing a windows' non-client area.
Remove the second SetSysColors(2, aElements, aOldColors); line of code, which reverts back to the orignal color and then try again. The code example you have seems almost identical to the MSDN link https://msdn.microsoft.com/en-us/library/windows/desktop/ms724940%28v=vs.85%29.aspx link minus the sleep. Their example shows how to set color, sleeps and then reverts back.

Windows Imaging Component - Direct2D C++ - Drawing, Saving

Using Windows Image Component (WIC), I want to do the following for my windows desktop application (Direct2D/C++ with Windows 7 SP1 - Visual Studio 2013)
Choose any type of RenderTarget (Direct2D Hwnd/Bitmap/WICBitmap -
etc) for drawing
Create a empty bitmap (D2D1Bitmap or IWICBitmap -
whichever applicable)
Begin draw - Fill colour, draw some lines and ellipses -
End draw ==> (All in the Bitmap)
At some point of time, I need to
save the drawn content in the bitmap as an image in my computer
Place the bitmap in the x1,y1 (top left - xy coordinates) and x2,y2
(bottom right - xy coordinates) of the render target. Because the
rest of the space of the window would be used by toolbar.
How do I achieve this using C++/Direct2D?
GDI+ Code for my functionality:
Bitmap* pBmp = NULL; //create null or empty bitmap
Graphics* pGrBuf = NULL; //initialise graphics to null
pBmp = new Bitmap((INT)rectClient.Width, (INT)rectClient.Height);
pGrBuf = new Graphics(pBmp);
On this Graphics, I could always draw Lines, Rectangles, etc..
pGrBuf.DrawRectangle(....)
pGrBuf.DrawLine(...)
In the end, for achieving point number 5
//leave some space (30, 30 in xy co-ordinates) for putting the toolbox in the top
pGrBuf.DrawImage(m_pBmp, 30.0f, 30.0f);
The code for point 4 is intentionally omitted.
The question have a simple, unambiguous answer, but there are some details that you should (re)consider.
Direct2D is not a panacea framework that will easily outperform others. It's not very clear what are your drawings about and whats their purpose, but there are cases where Direct2D usage is not very appropriate. If you replace GDI(+) with D2D, some of your sufferings will be:
(officialy) limited OS support, according to the DirectX version and/or the functions you will use. You will have to forget about Windows XP, (very possibly) Windows Vista and (less possibly) Windows 7
the performance (compared to GDI+, GDI) is not always greater. Mainly depends from the way and the purpose you use D2D. There are cases where D2D has very poor performance (usually wrong usage or misunderstood concepts).
But also, the advantages that Direct2D could provide are countless.
Roughly said, Direct2D is nothing but a wrapper around Direct3D. It was introduced with the DirectX 10 and its usage was very similar to GDI(+). But with DirectX 11(1), the Direct2D "principles" were changed and now its more D3D-like. It adds another approaches and drops old ones. It could be a little bit confusing at first. Confusing, also because all the tutorials, articles and whatever D2D resources (including MSDN) in the web are mixed up between the D2D versions. Some of them are for the old version and recommend one thing (approach), other describe the new version.
Anyway, I recommend the new version - ie Direct2D 11.1.
To your question...
The "RenderTarget" is a concept from the "old" D2D. The new one is a DeviceContext
The DeviceContext has a target that could be a D2D1Bitmap(1) - offscreen one, a swap chain's back buffer.
The most typical drawing approach is to call drawing functions within a DeviceContext.BeginScene --- DeviceContext.EndScene block. The drawing functions are very similar to the GDI(+) ones.
There are several ways to do that. You can do it with the help of WIC. Also you can copy your D2D1Bitmap data to a DIBBitmap or you can even (re)draw it over a GDI context.
There is a function DeviceContext.DrawImage, but the way you will do it depends on many things. For example, you could have two bitmaps, that are drawn over two different HWnd (one for the toolbar, another one for the other drawing).
Here are some resources that could help you:
What is Direct2D for
Drawing a rectangle with Direct2D
Very well explained guide about migrating to Direct2D 1.1
Answer to another question here, related to Direct2D, but explains in short the way you should draw to a HWnd

Different Window Form

I have been using directx for a while now and one thing that has always bothered me is that windows are squares (I guess this applies to most programs). Now as creation often happens by defining a rectangle shape and drawing that, black for example. I have been thinking of 2 approaches to this:
Define a bigger rectangle and draw parts of the background transparent.
I decided not to go for this one as I have absolutely no idea how to do this.
See what microsoft offers when it comes to window shapes.
And while they did have a lot of win32 configuration settings (no border etc) I couldn't find anything about drawing in a particular shape (like using a triangle for example).
Does anyone have experience with window shapes or drawing a background transparent? Maybe even a better option that I missed? Thanks in advance!
This can be done quite simply using the SetWindowRgn API call. What this does is define the area within which the system will allow drawing to appear.
As a simple example, lets punch a hole in one of our windows. The following can be done in the WM_CREATE handler of the window:
case WM_CREATE:
{
// Get the window rect
RECT r;
GetWindowRect(m_hwnd, &r);
MapWindowPoints(NULL, m_hwnd, reinterpret_cast<LPPOINT>(&r), 2);
// Work out the size of the window
LONG w = r.right - r.left;
LONG h = r.bottom - r.top;
// Create a rectangular region to cover the window (almost)
HRGN hRgn = CreateRectRgnIndirect(&r);
// and a smaller elliptical window
r.left += w/4;
r.right -= w/4;
r.top += h/4;
r.bottom -= h/4;
HRGN rgnCirc = CreateEllipticRgnIndirect(&r);
// Now we combine the two regions, using XOR to create a hole
int cres = CombineRgn(hRgn, rgnCirc, hRgn, RGN_XOR);
// And set the region.
SetWindowRgn(m_hwnd, hRgn, TRUE);
}
break;
Some important notes. The region that is passed to SetWindowRgn is from that point on owned by the system, so do not perform any more operations on it. Also, you would need to modify the region if the window is resized - I only put the example in WM_CREATE as... an example.
Another little caveat about the above, it doesn't perform the calculation of window size correctly... as I said, this is just an example that punches a hole in a window.
Finally, I tried it with a simple Direct-X program, and it works with that too. Hoorah!

Drawing on the screen

I'm currently developing an application with OpenCV to do visual recognition of elements on the screen.
While a visual representation of the process is not needed, it would be very useful for debugging purposes if I could find a way to draw circles, lines and possibly text directly on the screen, without having an app window.
There are certain applications that, for instance, draw HUDs over the screen. How do they go about doing that?
I need a way for my drawing to always be at the front. In general, all the ways I managed to find involve painting on a window (WinAPI, Direct2D, OpenGL). Is there a workaround to make it appear like it's simply a layover on the desktop (including all open windows)?
for the purpose of debugging, just literally draw on the screen. IIRC GetDC(0) will get you a device context for the screen, but check out that whole family of functions. in Windows 7 it doesn't even foul up other applications' displays, and reportedly it's likewise "safe" on the mac.
for example, this draws an ellipse in the upper left of the screen:
#include <windows.h>
int main()
{
HDC const dc = GetDC( 0 );
Ellipse( dc, 10, 10, 200, 200 );
}
the graphic disappears if it's on top of a window and that window is moved.
You can achieve the device context (DC) of the screen, and draw in that DC as usual. The output will be directed to the screen. To achieve that, call WinApi GetDC("DISPLAY"), if i'm not mistaken.

How to center the dialog caption?

I created my IDD_DIALOG in my resource file.
I am using the following code to center the DIALOG in the middle of a screen:
case WM_INITDIALOG:
RECT Rect;
::GetWindowRect(hwndDlg, &Rect);
::SetWindowPos(hwndDlg, HWND_TOPMOST, (::GetSystemMetrics(SM_CXSCREEN)/2 - ((Rect.right - Rect.left)/2)), (::GetSystemMetrics(SM_CYSCREEN)/2 - ((Rect.bottom - Rect.top)/2)), (Rect.right - Rect.left), (Rect.bottom - Rect.top), SWP_SHOWWINDOW);
return TRUE;
My question is, how would I also center the caption (dialog title) alongside with my current code?
Thanks.
Unless you really need to, it's best not to.
You'd likely need to paint a custom border/frame, which is what some apps like Office do so that they can have additional button widgets in the title bar. (Office also centers the text in its titlebar...) It's pretty complex, plus there's two different techniques depending on whether you're running on pre-XP or Vista with DWM enabled. I found an article that summarizes some of the issues involved in the Vista/DWM case. And in case DWM isn't enabled, you might need to support the old technique, handling and overriding WM_NCPAINT.
That's a lot of work just to center a titlebar!
Turns out that the justification for the titlebar has changed over time: in Win3.1, it was centered; then in the 95 era it became left-justified (or right-justified for right-to-left languages). Turns out that things have come full circle, and in Desktop mode, Windows 8 reverts to centering it again.
So perhaps simplest answer to "How to center the dialog caption?" is: upgrade to Windows 8 :-)