aboutsummaryrefslogtreecommitdiff
path: root/src/samples
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2021-04-22 17:57:31 -0700
committerRob Mensching <rob@firegiant.com>2021-04-29 16:49:19 -0700
commit03d5c46cbbb94f73ac468709345fc6a0e50def8d (patch)
tree9460f73cc308ead863e7a28a68fdeb6d85a2e69e /src/samples
parent35606d2cd04a7b1bec1d669f9619501dff2bf9dc (diff)
downloadwix-03d5c46cbbb94f73ac468709345fc6a0e50def8d.tar.gz
wix-03d5c46cbbb94f73ac468709345fc6a0e50def8d.tar.bz2
wix-03d5c46cbbb94f73ac468709345fc6a0e50def8d.zip
Move Tools into wix
Diffstat (limited to 'src/samples')
-rw-r--r--src/samples/ThmViewerPackage/Package.wxs30
-rw-r--r--src/samples/ThmViewerPackage/ThmViewerPackage.wixproj35
-rw-r--r--src/samples/ThmViewerPackage/packages.config4
-rw-r--r--src/samples/thmviewer/Resources/LoremIpsum.rtfbin0 -> 4870 bytes
-rw-r--r--src/samples/thmviewer/Resources/thm.xml11
-rw-r--r--src/samples/thmviewer/display.cpp354
-rw-r--r--src/samples/thmviewer/load.cpp221
-rw-r--r--src/samples/thmviewer/packages.config8
-rw-r--r--src/samples/thmviewer/precomp.cpp3
-rw-r--r--src/samples/thmviewer/precomp.h70
-rw-r--r--src/samples/thmviewer/resource.h16
-rw-r--r--src/samples/thmviewer/thmviewer.cpp543
-rw-r--r--src/samples/thmviewer/thmviewer.manifest19
-rw-r--r--src/samples/thmviewer/thmviewer.rc12
-rw-r--r--src/samples/thmviewer/thmviewer.v3.ncrunchproject7
-rw-r--r--src/samples/thmviewer/thmviewer.vcxproj88
-rw-r--r--src/samples/thmviewer/thmviewer.vcxproj.filters56
17 files changed, 1477 insertions, 0 deletions
diff --git a/src/samples/ThmViewerPackage/Package.wxs b/src/samples/ThmViewerPackage/Package.wxs
new file mode 100644
index 00000000..cb2139cf
--- /dev/null
+++ b/src/samples/ThmViewerPackage/Package.wxs
@@ -0,0 +1,30 @@
1<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Package Name="WiX Toolset Theme Viewer" Manufacturer="WiX Toolset" Language="1033" Version="!(bind.fileVersion.ThmViewerFile)" UpgradeCode="59c4b122-5167-445b-8fc4-09dcd4eced89" Compressed="yes" InstallerVersion="200">
4 <MajorUpgrade DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
5 <MediaTemplate EmbedCab="yes" />
6
7 <Feature Id="Main">
8 <ComponentGroupRef Id="Components" />
9 </Feature>
10 </Package>
11
12 <Fragment>
13 <ComponentGroup Id="Components" Directory="INSTALLFOLDER" Subdirectory="bin">
14 <Component>
15 <File Id="ThmViewerFile" Source="thmviewer.exe" />
16 <Shortcut Name="!(bind.property.ProductName)" Directory="ShortcutFolder" Advertise="yes" />
17 </Component>
18 </ComponentGroup>
19 </Fragment>
20
21 <Fragment>
22 <StandardDirectory Id="ProgramFilesFolder">
23 <Directory Id="INSTALLFOLDER" Name="WiX Toolset v4.0" />
24 </StandardDirectory>
25 <StandardDirectory Id="ProgramMenuFolder">
26 <Directory Id="ShortcutFolder" Name="WiX Toolset" />
27 </StandardDirectory>
28 </Fragment>
29
30</Wix>
diff --git a/src/samples/ThmViewerPackage/ThmViewerPackage.wixproj b/src/samples/ThmViewerPackage/ThmViewerPackage.wixproj
new file mode 100644
index 00000000..23d24654
--- /dev/null
+++ b/src/samples/ThmViewerPackage/ThmViewerPackage.wixproj
@@ -0,0 +1,35 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
4
5 <PropertyGroup>
6 <ProjectGuid>59c4b122-5167-445b-8fc4-09dcd4eced89</ProjectGuid>
7 <OutputName>thmviewer</OutputName>
8 <OutputType>Package</OutputType>
9 </PropertyGroup>
10
11 <ItemGroup>
12 <Compile Include="Package.wxs" />
13 </ItemGroup>
14
15 <ItemGroup>
16 <None Include="packages.config" />
17 </ItemGroup>
18
19 <ItemGroup>
20 <ProjectReference Include="..\thmviewer\thmviewer.vcxproj">
21 <Name>thmviewer</Name>
22 <Project>{95228C13-97F5-484A-B4A2-ECF4618B0881}</Project>
23 </ProjectReference>
24 </ItemGroup>
25
26 <Import Project="$(WixTargetsPath)" />
27
28 <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
29 <PropertyGroup>
30 <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
31 </PropertyGroup>
32 <Error Condition="!Exists('..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets'))" />
33 </Target>
34 <Import Project="..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets" Condition="Exists('..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" />
35</Project>
diff --git a/src/samples/ThmViewerPackage/packages.config b/src/samples/ThmViewerPackage/packages.config
new file mode 100644
index 00000000..7e55f706
--- /dev/null
+++ b/src/samples/ThmViewerPackage/packages.config
@@ -0,0 +1,4 @@
1<?xml version="1.0" encoding="utf-8"?>
2<packages>
3 <package id="Nerdbank.GitVersioning" version="3.3.37" developmentDependency="true" targetFramework="net40" />
4</packages> \ No newline at end of file
diff --git a/src/samples/thmviewer/Resources/LoremIpsum.rtf b/src/samples/thmviewer/Resources/LoremIpsum.rtf
new file mode 100644
index 00000000..1ab0e65b
--- /dev/null
+++ b/src/samples/thmviewer/Resources/LoremIpsum.rtf
Binary files differ
diff --git a/src/samples/thmviewer/Resources/thm.xml b/src/samples/thmviewer/Resources/thm.xml
new file mode 100644
index 00000000..6394f0f1
--- /dev/null
+++ b/src/samples/thmviewer/Resources/thm.xml
@@ -0,0 +1,11 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<Theme>
6 <Font Id="0" Height="-48" Weight="700" Foreground="$windowtext">Consolas</Font>
7 <Font Id="1" Height="-12" Weight="700" Foreground="$windowtext" Background="$window">Consolas</Font>
8 <Window Width="220" Height="200" FontId="0" Caption="Theme Viewer">
9 <TreeView Name="Tree" X="0" Y="0" Width="0" Height="0" FontId="1" Visible="yes" FullRowSelect="yes" HasButtons="yes" AlwaysShowSelect="yes" HasLines="yes" LinesAtRoot="yes"/>
10 </Window>
11</Theme>
diff --git a/src/samples/thmviewer/display.cpp b/src/samples/thmviewer/display.cpp
new file mode 100644
index 00000000..52fa3cf8
--- /dev/null
+++ b/src/samples/thmviewer/display.cpp
@@ -0,0 +1,354 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5static const LPCWSTR THMVWR_WINDOW_CLASS_DISPLAY = L"ThmViewerDisplay";
6
7struct DISPLAY_THREAD_CONTEXT
8{
9 HWND hWnd;
10 HINSTANCE hInstance;
11
12 HANDLE hInit;
13};
14
15static DWORD WINAPI DisplayThreadProc(
16 __in LPVOID pvContext
17 );
18static LRESULT CALLBACK DisplayWndProc(
19 __in HWND hWnd,
20 __in UINT uMsg,
21 __in WPARAM wParam,
22 __in LPARAM lParam
23 );
24static BOOL DisplayOnCreate(
25 __in THEME* pTheme,
26 __in HWND hWnd
27 );
28
29
30extern "C" HRESULT DisplayStart(
31 __in HINSTANCE hInstance,
32 __in HWND hWnd,
33 __out HANDLE *phThread,
34 __out DWORD* pdwThreadId
35 )
36{
37 HRESULT hr = S_OK;
38 HANDLE rgHandles[2] = { };
39 DISPLAY_THREAD_CONTEXT context = { };
40
41 rgHandles[0] = ::CreateEventW(NULL, TRUE, FALSE, NULL);
42 ExitOnNullWithLastError(rgHandles[0], hr, "Failed to create load init event.");
43
44 context.hWnd = hWnd;
45 context.hInstance = hInstance;
46 context.hInit = rgHandles[0];
47
48 rgHandles[1] = ::CreateThread(NULL, 0, DisplayThreadProc, reinterpret_cast<LPVOID>(&context), 0, pdwThreadId);
49 ExitOnNullWithLastError(rgHandles[1], hr, "Failed to create display thread.");
50
51 ::WaitForMultipleObjects(countof(rgHandles), rgHandles, FALSE, INFINITE);
52
53 *phThread = rgHandles[1];
54 rgHandles[1] = NULL;
55
56LExit:
57 ReleaseHandle(rgHandles[1]);
58 ReleaseHandle(rgHandles[0]);
59 return hr;
60}
61
62static DWORD WINAPI DisplayThreadProc(
63 __in LPVOID pvContext
64 )
65{
66 HRESULT hr = S_OK;
67
68 DISPLAY_THREAD_CONTEXT* pContext = static_cast<DISPLAY_THREAD_CONTEXT*>(pvContext);
69 HINSTANCE hInstance = pContext->hInstance;
70 HWND hwndParent = pContext->hWnd;
71
72 // We can signal the initialization event as soon as we have copied the context
73 // values into local variables.
74 ::SetEvent(pContext->hInit);
75
76 BOOL fComInitialized = FALSE;
77
78 HANDLE_THEME* pCurrentHandle = NULL;
79 ATOM atomWc = 0;
80 WNDCLASSW wc = { }; // the following are constant for the display window class.
81 wc.lpfnWndProc = DisplayWndProc;
82 wc.hInstance = hInstance;
83 wc.lpszClassName = THMVWR_WINDOW_CLASS_DISPLAY;
84
85 HWND hWnd = NULL;
86 RECT rc = { };
87 int x = CW_USEDEFAULT;
88 int y = CW_USEDEFAULT;
89
90 BOOL fRedoMsg = FALSE;
91 BOOL fRet = FALSE;
92 MSG msg = { };
93
94 BOOL fCreateIfNecessary = FALSE;
95
96 hr = ::CoInitialize(NULL);
97 ExitOnFailure(hr, "Failed to initialize COM on display thread.");
98 fComInitialized = TRUE;
99
100 // As long as the parent window is alive and kicking, keep this thread going (with or without a theme to display ).
101 while (::IsWindow(hwndParent))
102 {
103 if (pCurrentHandle && fCreateIfNecessary)
104 {
105 THEME* pTheme = pCurrentHandle->pTheme;
106
107 if (CW_USEDEFAULT == x && CW_USEDEFAULT == y && ::GetWindowRect(hwndParent, &rc))
108 {
109 x = rc.left;
110 y = rc.bottom + 20;
111 }
112
113 hr = ThemeCreateParentWindow(pTheme, 0, wc.lpszClassName, pTheme->sczCaption, pTheme->dwStyle, x, y, hwndParent, hInstance, pCurrentHandle, THEME_WINDOW_INITIAL_POSITION_DEFAULT, &hWnd);
114 ExitOnFailure(hr, "Failed to create display window.");
115
116 fCreateIfNecessary = FALSE;
117 }
118
119 // message pump
120 while (fRedoMsg || 0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0)))
121 {
122 if (fRedoMsg)
123 {
124 fRedoMsg = FALSE;
125 }
126
127 if (-1 == fRet)
128 {
129 hr = E_UNEXPECTED;
130 ExitOnFailure(hr, "Unexpected return value from display message pump.");
131 }
132 else if (NULL == msg.hwnd) // Thread message.
133 {
134 if (WM_THMVWR_NEW_THEME == msg.message)
135 {
136 // If there is already a handle, release it.
137 if (pCurrentHandle)
138 {
139 DecrementHandleTheme(pCurrentHandle);
140 pCurrentHandle = NULL;
141 }
142
143 // If the window was created, remember its window location before we destroy
144 // it so so we can open the new window in the same place.
145 if (::IsWindow(hWnd))
146 {
147 ::GetWindowRect(hWnd, &rc);
148 x = rc.left;
149 y = rc.top;
150
151 ::DestroyWindow(hWnd);
152 }
153
154 // If the display window class was registered, unregister it so we can
155 // reuse the same window class name for the new theme.
156 if (atomWc)
157 {
158 if (!::UnregisterClassW(reinterpret_cast<LPCWSTR>(atomWc), hInstance))
159 {
160 DWORD er = ::GetLastError();
161 er = er;
162 }
163
164 atomWc = 0;
165 }
166
167 // If we were provided a new theme handle, create a new window class to
168 // support it.
169 pCurrentHandle = reinterpret_cast<HANDLE_THEME*>(msg.lParam);
170 if (pCurrentHandle)
171 {
172 wc.hIcon = reinterpret_cast<HICON>(pCurrentHandle->pTheme->hIcon);
173 wc.hCursor = ::LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
174 if (0 < pCurrentHandle->pTheme->cFonts)
175 {
176 wc.hbrBackground = pCurrentHandle->pTheme->rgFonts[pCurrentHandle->pTheme->dwFontId].hBackground;
177 }
178 atomWc = ::RegisterClassW(&wc);
179 if (!atomWc)
180 {
181 ExitWithLastError(hr, "Failed to register display window class.");
182 }
183 }
184 }
185 else if (WM_THMVWR_SHOWPAGE == msg.message)
186 {
187 if (pCurrentHandle && ::IsWindow(hWnd) && pCurrentHandle->pTheme->hwndParent == hWnd)
188 {
189 DWORD dwPageId = static_cast<DWORD>(msg.lParam);
190 int nCmdShow = static_cast<int>(msg.wParam);
191
192 // First show/hide the controls not associated with a page.
193 for (DWORD i = 0; i < pCurrentHandle->pTheme->cControls; ++i)
194 {
195 THEME_CONTROL* pControl = pCurrentHandle->pTheme->rgControls + i;
196 if (!pControl->wPageId)
197 {
198 ThemeShowControl(pCurrentHandle->pTheme, pControl->wId, nCmdShow);
199 }
200 }
201
202 // If a page id was provided also, show/hide those controls
203 if (dwPageId)
204 {
205 // Ignore error since we aren't using variables and it can only fail when using variables.
206 ThemeShowPage(pCurrentHandle->pTheme, dwPageId, nCmdShow);
207 }
208 }
209 else // display window isn't visible or it doesn't match the current handle.
210 {
211 // Keep the current message around to try again after we break out of this loop
212 // and create the window.
213 fRedoMsg = TRUE;
214 fCreateIfNecessary = TRUE;
215 break;
216 }
217 }
218 }
219 else if (!ThemeHandleKeyboardMessage(pCurrentHandle->pTheme, hwndParent, &msg)) // Window message.
220 {
221 ::TranslateMessage(&msg);
222 ::DispatchMessageW(&msg);
223 }
224 }
225 }
226
227LExit:
228 if (::IsWindow(hWnd))
229 {
230 ::DestroyWindow(hWnd);
231 }
232
233 if (atomWc)
234 {
235 if (!::UnregisterClassW(THMVWR_WINDOW_CLASS_DISPLAY, hInstance))
236 {
237 DWORD er = ::GetLastError();
238 er = er;
239 }
240 }
241
242 DecrementHandleTheme(pCurrentHandle);
243
244 if (fComInitialized)
245 {
246 ::CoUninitialize();
247 }
248
249 return hr;
250}
251
252static LRESULT CALLBACK DisplayWndProc(
253 __in HWND hWnd,
254 __in UINT uMsg,
255 __in WPARAM wParam,
256 __in LPARAM lParam
257 )
258{
259 static DWORD dwProgress = 0;
260 HANDLE_THEME* pHandleTheme = reinterpret_cast<HANDLE_THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
261
262 switch (uMsg)
263 {
264 case WM_NCCREATE:
265 {
266 LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
267 pHandleTheme = reinterpret_cast<HANDLE_THEME*>(lpcs->lpCreateParams);
268 IncrementHandleTheme(pHandleTheme);
269 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pHandleTheme));
270 }
271 break;
272
273 case WM_CREATE:
274 if (!DisplayOnCreate(pHandleTheme->pTheme, hWnd))
275 {
276 return -1;
277 }
278 break;
279
280 case WM_TIMER:
281 if (!lParam && SUCCEEDED(ThemeSetProgressControl(pHandleTheme->pTheme, wParam, dwProgress)))
282 {
283 dwProgress += rand() % 10 + 1;
284 if (dwProgress > 100)
285 {
286 dwProgress = 0;
287 }
288
289 return 0;
290 }
291 break;
292
293 case WM_COMMAND:
294 {
295 WCHAR wzText[1024];
296 ::StringCchPrintfW(wzText, countof(wzText), L"Command %u\r\n", LOWORD(wParam));
297 OutputDebugStringW(wzText);
298 //::MessageBoxW(hWnd, wzText, L"Command fired", MB_OK);
299 }
300 break;
301
302 case WM_SYSCOMMAND:
303 {
304 WCHAR wzText[1024];
305 ::StringCchPrintfW(wzText, countof(wzText), L"SysCommand %u\r\n", LOWORD(wParam));
306 OutputDebugStringW(wzText);
307 //::MessageBoxW(hWnd, wzText, L"Command fired", MB_OK);
308 }
309 break;
310
311 case WM_DESTROY:
312 ThemeUnloadControls(pHandleTheme->pTheme);
313 ::PostQuitMessage(0);
314 break;
315
316 case WM_NCDESTROY:
317 DecrementHandleTheme(pHandleTheme);
318 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
319 break;
320 }
321
322 return ThemeDefWindowProc(pHandleTheme ? pHandleTheme->pTheme : NULL, hWnd, uMsg, wParam, lParam);
323}
324
325static BOOL DisplayOnCreate(
326 __in THEME* pTheme,
327 __in HWND hWnd
328 )
329{
330 HRESULT hr = S_OK;
331
332 hr = ThemeLoadControls(pTheme, NULL, 0);
333 ExitOnFailure(hr, "Failed to load theme controls");
334
335 // Pre-populate some control types with data.
336 for (DWORD i = 0; i < pTheme->cControls; ++i)
337 {
338 THEME_CONTROL* pControl = pTheme->rgControls + i;
339 if (THEME_CONTROL_TYPE_RICHEDIT == pControl->type)
340 {
341 hr = ThemeLoadRichEditFromResource(pTheme, pControl->wId, MAKEINTRESOURCEA(THMVWR_RES_RICHEDIT_FILE), ::GetModuleHandleW(NULL));
342 ExitOnFailure(hr, "Failed to load richedit text.");
343 }
344 else if (THEME_CONTROL_TYPE_PROGRESSBAR == pControl->type)
345 {
346 DWORD dwId = ::SetTimer(hWnd, pControl->wId, 500, NULL);
347 dwId = dwId; // prevents warning in "ship" build.
348 Assert(dwId == pControl->wId);
349 }
350 }
351
352LExit:
353 return SUCCEEDED(hr);
354}
diff --git a/src/samples/thmviewer/load.cpp b/src/samples/thmviewer/load.cpp
new file mode 100644
index 00000000..0267402a
--- /dev/null
+++ b/src/samples/thmviewer/load.cpp
@@ -0,0 +1,221 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5struct LOAD_THREAD_CONTEXT
6{
7 HWND hWnd;
8 LPCWSTR wzThemePath;
9 LPCWSTR wzWxlPath;
10
11 HANDLE hInit;
12};
13
14static DWORD WINAPI LoadThreadProc(
15 __in LPVOID pvContext
16 );
17
18
19extern "C" HRESULT LoadStart(
20 __in_z LPCWSTR wzThemePath,
21 __in_z_opt LPCWSTR wzWxlPath,
22 __in HWND hWnd,
23 __out HANDLE* phThread
24 )
25{
26 HRESULT hr = S_OK;
27 HANDLE rgHandles[2] = { };
28 LOAD_THREAD_CONTEXT context = { };
29
30 rgHandles[0] = ::CreateEventW(NULL, TRUE, FALSE, NULL);
31 ExitOnNullWithLastError(rgHandles[0], hr, "Failed to create load init event.");
32
33 context.hWnd = hWnd;
34 context.wzThemePath = wzThemePath;
35 context.wzWxlPath = wzWxlPath;
36 context.hInit = rgHandles[0];
37
38 rgHandles[1] = ::CreateThread(NULL, 0, LoadThreadProc, &context, 0, NULL);
39 ExitOnNullWithLastError(rgHandles[1], hr, "Failed to create load thread.");
40
41 ::WaitForMultipleObjects(countof(rgHandles), rgHandles, FALSE, INFINITE);
42
43 *phThread = rgHandles[1];
44 rgHandles[1] = NULL;
45
46LExit:
47 ReleaseHandle(rgHandles[1]);
48 ReleaseHandle(rgHandles[0]);
49 return hr;
50}
51
52
53static DWORD WINAPI LoadThreadProc(
54 __in LPVOID pvContext
55 )
56{
57 HRESULT hr = S_OK;
58 WIX_LOCALIZATION* pWixLoc = NULL;
59 LPWSTR sczThemePath = NULL;
60 LPWSTR sczWxlPath = NULL;
61 BOOL fComInitialized = FALSE;
62 HANDLE hDirectory = INVALID_HANDLE_VALUE;
63 LPWSTR sczDirectory = NULL;
64 LPWSTR wzFileName = NULL;
65
66 THEME* pTheme = NULL;
67 HANDLE_THEME* pHandle = NULL;
68
69 LOAD_THREAD_CONTEXT* pContext = static_cast<LOAD_THREAD_CONTEXT*>(pvContext);
70 HWND hWnd = pContext->hWnd;
71
72 hr = StrAllocString(&sczThemePath, pContext->wzThemePath, 0);
73 ExitOnFailure(hr, "Failed to copy path to initial theme file.");
74
75 if (pContext->wzWxlPath)
76 {
77 hr = StrAllocString(&sczWxlPath, pContext->wzWxlPath, 0);
78 ExitOnFailure(hr, "Failed to copy .wxl path to initial file.");
79 }
80
81 // We can signal the initialization event as soon as we have copied the context
82 // values into local variables.
83 ::SetEvent(pContext->hInit);
84
85 hr = ::CoInitialize(NULL);
86 ExitOnFailure(hr, "Failed to initialize COM on load thread.");
87 fComInitialized = TRUE;
88
89 // Open a handle to the directory so we can put a notification on it.
90 hr = PathGetDirectory(sczThemePath, &sczDirectory);
91 ExitOnFailure(hr, "Failed to get path directory.");
92
93 wzFileName = PathFile(sczThemePath);
94
95 hDirectory = ::CreateFileW(sczDirectory, FILE_LIST_DIRECTORY, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
96 if (INVALID_HANDLE_VALUE == hDirectory)
97 {
98 ExitWithLastError(hr, "Failed to open directory: %ls", sczDirectory);
99 }
100
101 BOOL fUpdated = FALSE;
102 do
103 {
104 // Get the last modified time on the file we're loading for verification that the
105 // file actually gets changed down below.
106 FILETIME ftModified = { };
107 FileGetTime(sczThemePath, NULL, NULL, &ftModified);
108
109 ::SendMessageW(hWnd, WM_THMVWR_THEME_LOAD_BEGIN, 0, 0);
110
111 // Try to load the theme file.
112 hr = ThemeLoadFromFile(sczThemePath, &pTheme);
113 if (FAILED(hr))
114 {
115 ::SendMessageW(hWnd, WM_THMVWR_THEME_LOAD_ERROR, 0, hr);
116 }
117 else
118 {
119 if (sczWxlPath)
120 {
121 hr = LocLoadFromFile(sczWxlPath, &pWixLoc);
122 ExitOnFailure(hr, "Failed to load loc file from path: %ls", sczWxlPath);
123
124 hr = ThemeLocalize(pTheme, pWixLoc);
125 ExitOnFailure(hr, "Failed to localize theme: %ls", sczWxlPath);
126 }
127
128 hr = AllocHandleTheme(pTheme, &pHandle);
129 ExitOnFailure(hr, "Failed to allocate handle to theme");
130
131 ::SendMessageW(hWnd, WM_THMVWR_NEW_THEME, 0, reinterpret_cast<LPARAM>(pHandle));
132 pHandle = NULL;
133 }
134
135 fUpdated = FALSE;
136 do
137 {
138 DWORD rgbNotifications[1024];
139 DWORD cbRead = 0;
140 if (!::ReadDirectoryChangesW(hDirectory, rgbNotifications, sizeof(rgbNotifications), FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, &cbRead, NULL, NULL))
141 {
142 ExitWithLastError(hr, "Failed while watching directory: %ls", sczDirectory);
143 }
144
145 // Wait for half a second to let all the file handles get closed to minimize access
146 // denied errors.
147 ::Sleep(500);
148
149 FILE_NOTIFY_INFORMATION* pNotification = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(rgbNotifications);
150 while (pNotification)
151 {
152 // If our file was updated, check to see if the modified time really changed. The notifications
153 // are often trigger happy thinking the file changed two or three times in a row. Maybe it's AV
154 // software creating the problems but actually checking the modified date works well.
155 if (CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, NORM_IGNORECASE, pNotification->FileName, pNotification->FileNameLength / sizeof(WCHAR), wzFileName, -1))
156 {
157 FILETIME ft = { };
158 FileGetTime(sczThemePath, NULL, NULL, &ft);
159
160 fUpdated = (ftModified.dwHighDateTime < ft.dwHighDateTime) || (ftModified.dwHighDateTime == ft.dwHighDateTime && ftModified.dwLowDateTime < ft.dwLowDateTime);
161 break;
162 }
163
164 pNotification = pNotification->NextEntryOffset ? reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<BYTE*>(pNotification) + pNotification->NextEntryOffset) : NULL;
165 }
166 } while (!fUpdated);
167 } while(fUpdated);
168
169LExit:
170 if (fComInitialized)
171 {
172 ::CoUninitialize();
173 }
174
175 LocFree(pWixLoc);
176 ReleaseFileHandle(hDirectory);
177 ReleaseStr(sczDirectory);
178 ReleaseStr(sczThemePath);
179 ReleaseStr(sczWxlPath);
180 return hr;
181}
182
183extern "C" HRESULT AllocHandleTheme(
184 __in THEME* pTheme,
185 __out HANDLE_THEME** ppHandle
186 )
187{
188 HRESULT hr = S_OK;
189 HANDLE_THEME* pHandle = NULL;
190
191 pHandle = static_cast<HANDLE_THEME*>(MemAlloc(sizeof(HANDLE_THEME), TRUE));
192 ExitOnNull(pHandle, hr, E_OUTOFMEMORY, "Failed to allocate theme handle.");
193
194 pHandle->cReferences = 1;
195 pHandle->pTheme = pTheme;
196
197 *ppHandle = pHandle;
198 pHandle = NULL;
199
200LExit:
201 ReleaseMem(pHandle);
202 return hr;
203}
204
205extern "C" void IncrementHandleTheme(
206 __in HANDLE_THEME* pHandle
207 )
208{
209 ::InterlockedIncrement(reinterpret_cast<LONG*>(&pHandle->cReferences));
210}
211
212extern "C" void DecrementHandleTheme(
213 __in HANDLE_THEME* pHandle
214 )
215{
216 if (pHandle && 0 == ::InterlockedDecrement(reinterpret_cast<LONG*>(&pHandle->cReferences)))
217 {
218 ThemeFree(pHandle->pTheme);
219 MemFree(pHandle);
220 }
221}
diff --git a/src/samples/thmviewer/packages.config b/src/samples/thmviewer/packages.config
new file mode 100644
index 00000000..a98c0c8e
--- /dev/null
+++ b/src/samples/thmviewer/packages.config
@@ -0,0 +1,8 @@
1<?xml version="1.0" encoding="utf-8"?>
2<packages>
3 <package id="Microsoft.Build.Tasks.Git" version="1.0.0" targetFramework="native" developmentDependency="true" />
4 <package id="Microsoft.SourceLink.Common" version="1.0.0" targetFramework="native" developmentDependency="true" />
5 <package id="Microsoft.SourceLink.GitHub" version="1.0.0" targetFramework="native" developmentDependency="true" />
6 <package id="Nerdbank.GitVersioning" version="3.3.37" targetFramework="native" developmentDependency="true" />
7 <package id="WixToolset.DUtil" version="4.0.72" targetFramework="native" />
8</packages> \ No newline at end of file
diff --git a/src/samples/thmviewer/precomp.cpp b/src/samples/thmviewer/precomp.cpp
new file mode 100644
index 00000000..37664a1c
--- /dev/null
+++ b/src/samples/thmviewer/precomp.cpp
@@ -0,0 +1,3 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
diff --git a/src/samples/thmviewer/precomp.h b/src/samples/thmviewer/precomp.h
new file mode 100644
index 00000000..15d889fc
--- /dev/null
+++ b/src/samples/thmviewer/precomp.h
@@ -0,0 +1,70 @@
1#pragma once
2// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
3
4
5#include <windows.h>
6#include <msiquery.h>
7#include <objbase.h>
8#include <shlobj.h>
9#include <shlwapi.h>
10#include <stdlib.h>
11#include <strsafe.h>
12
13#pragma warning(push)
14#pragma warning(disable:4458)
15#include <gdiplus.h>
16#pragma warning(pop)
17
18#include "dutil.h"
19#include "apputil.h"
20#include "memutil.h"
21#include "dirutil.h"
22#include "fileutil.h"
23#include "locutil.h"
24#include "logutil.h"
25#include "pathutil.h"
26#include "resrutil.h"
27#include "shelutil.h"
28#include "strutil.h"
29#include "thmutil.h"
30
31#include "resource.h"
32
33struct HANDLE_THEME
34{
35 DWORD cReferences;
36 THEME* pTheme;
37};
38
39enum WM_THMVWR
40{
41 WM_THMVWR_SHOWPAGE = WM_APP,
42 WM_THMVWR_PARSE_FILE,
43 WM_THMVWR_NEW_THEME,
44 WM_THMVWR_THEME_LOAD_ERROR,
45 WM_THMVWR_THEME_LOAD_BEGIN,
46};
47
48extern "C" HRESULT DisplayStart(
49 __in HINSTANCE hInstance,
50 __in HWND hWnd,
51 __out HANDLE *phThread,
52 __out DWORD* pdwThreadId
53 );
54extern "C" HRESULT LoadStart(
55 __in_z LPCWSTR wzThemePath,
56 __in_z LPCWSTR wzWxlPath,
57 __in HWND hWnd,
58 __out HANDLE* phThread
59 );
60
61extern "C" HRESULT AllocHandleTheme(
62 __in THEME* pTheme,
63 __out HANDLE_THEME** ppHandle
64 );
65extern "C" void IncrementHandleTheme(
66 __in HANDLE_THEME* pHandle
67 );
68extern "C" void DecrementHandleTheme(
69 __in HANDLE_THEME* pHandle
70 );
diff --git a/src/samples/thmviewer/resource.h b/src/samples/thmviewer/resource.h
new file mode 100644
index 00000000..4acc32cc
--- /dev/null
+++ b/src/samples/thmviewer/resource.h
@@ -0,0 +1,16 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#define IDC_STATIC -1
4#define THMVWR_RES_THEME_FILE 1
5#define THMVWR_RES_RICHEDIT_FILE 2
6
7// Next default values for new objects
8//
9#ifdef APSTUDIO_INVOKED
10#ifndef APSTUDIO_READONLY_SYMBOLS
11#define _APS_NEXT_RESOURCE_VALUE 102
12#define _APS_NEXT_COMMAND_VALUE 40001
13#define _APS_NEXT_CONTROL_VALUE 1003
14#define _APS_NEXT_SYMED_VALUE 101
15#endif
16#endif
diff --git a/src/samples/thmviewer/thmviewer.cpp b/src/samples/thmviewer/thmviewer.cpp
new file mode 100644
index 00000000..f83182d3
--- /dev/null
+++ b/src/samples/thmviewer/thmviewer.cpp
@@ -0,0 +1,543 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include "precomp.h"
4
5static const LPCWSTR THMVWR_WINDOW_CLASS_MAIN = L"ThmViewerMain";
6
7static THEME* vpTheme = NULL;
8static DWORD vdwDisplayThreadId = 0;
9static LPWSTR vsczThemeLoadErrors = NULL;
10
11enum THMVWR_CONTROL
12{
13 // Non-paged controls
14 THMVWR_CONTROL_TREE = THEME_FIRST_ASSIGN_CONTROL_ID,
15};
16
17static THEME_ASSIGN_CONTROL_ID vrgInitControls[] = {
18 { THMVWR_CONTROL_TREE, L"Tree" },
19};
20
21// Internal functions
22
23static HRESULT ProcessCommandLine(
24 __in_z_opt LPCWSTR wzCommandLine,
25 __out_z LPWSTR* psczThemeFile,
26 __out_z LPWSTR* psczWxlFile
27 );
28static HRESULT CreateTheme(
29 __in HINSTANCE hInstance,
30 __out THEME** ppTheme
31 );
32static HRESULT CreateMainWindowClass(
33 __in HINSTANCE hInstance,
34 __in THEME* pTheme,
35 __out ATOM* pAtom
36 );
37static LRESULT CALLBACK MainWndProc(
38 __in HWND hWnd,
39 __in UINT uMsg,
40 __in WPARAM wParam,
41 __in LPARAM lParam
42 );
43static void OnThemeLoadBegin(
44 __in_z_opt LPWSTR sczThemeLoadErrors
45 );
46static void OnThemeLoadError(
47 __in THEME* pTheme,
48 __in HRESULT hrFailure
49 );
50static void OnNewTheme(
51 __in THEME* pTheme,
52 __in HWND hWnd,
53 __in HANDLE_THEME* pHandle
54 );
55static void CALLBACK ThmviewerTraceError(
56 __in_z LPCSTR szFile,
57 __in int iLine,
58 __in REPORT_LEVEL rl,
59 __in UINT source,
60 __in HRESULT hrError,
61 __in_z __format_string LPCSTR szFormat,
62 __in va_list args
63 );
64
65
66int WINAPI wWinMain(
67 __in HINSTANCE hInstance,
68 __in_opt HINSTANCE /* hPrevInstance */,
69 __in_z LPWSTR lpCmdLine,
70 __in int /*nCmdShow*/
71 )
72{
73 ::HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);
74
75 HRESULT hr = S_OK;
76 BOOL fComInitialized = FALSE;
77 LPWSTR sczThemeFile = NULL;
78 LPWSTR sczWxlFile = NULL;
79 ATOM atom = 0;
80 HWND hWnd = NULL;
81
82 HANDLE hDisplayThread = NULL;
83 HANDLE hLoadThread = NULL;
84
85 BOOL fRet = FALSE;
86 MSG msg = { };
87
88 hr = ::CoInitialize(NULL);
89 ExitOnFailure(hr, "Failed to initialize COM.");
90 fComInitialized = TRUE;
91
92 DutilInitialize(&ThmviewerTraceError);
93
94 hr = ProcessCommandLine(lpCmdLine, &sczThemeFile, &sczWxlFile);
95 ExitOnFailure(hr, "Failed to process command line.");
96
97 hr = CreateTheme(hInstance, &vpTheme);
98 ExitOnFailure(hr, "Failed to create theme.");
99
100 hr = CreateMainWindowClass(hInstance, vpTheme, &atom);
101 ExitOnFailure(hr, "Failed to create main window.");
102
103 hr = ThemeCreateParentWindow(vpTheme, 0, reinterpret_cast<LPCWSTR>(atom), vpTheme->sczCaption, vpTheme->dwStyle, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, hInstance, NULL, THEME_WINDOW_INITIAL_POSITION_DEFAULT, &hWnd);
104 ExitOnFailure(hr, "Failed to create window.");
105
106 if (!sczThemeFile)
107 {
108 // Prompt for a path to the theme file.
109 OPENFILENAMEW ofn = { };
110 WCHAR wzFile[MAX_PATH] = { };
111
112 ofn.lStructSize = sizeof(ofn);
113 ofn.hwndOwner = hWnd;
114 ofn.lpstrFile = wzFile;
115 ofn.nMaxFile = countof(wzFile);
116 ofn.lpstrFilter = L"Theme Files (*.thm)\0*.thm\0XML Files (*.xml)\0*.xml\0All Files (*.*)\0*.*\0";
117 ofn.nFilterIndex = 1;
118 ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
119 ofn.lpstrTitle = vpTheme->sczCaption;
120
121 if (::GetOpenFileNameW(&ofn))
122 {
123 hr = StrAllocString(&sczThemeFile, wzFile, 0);
124 ExitOnFailure(hr, "Failed to copy opened file to theme file.");
125 }
126 else
127 {
128 ::MessageBoxW(hWnd, L"Must specify a path to theme file.", vpTheme->sczCaption, MB_OK | MB_ICONERROR);
129 ExitFunction1(hr = E_INVALIDARG);
130 }
131 }
132
133 hr = DisplayStart(hInstance, hWnd, &hDisplayThread, &vdwDisplayThreadId);
134 ExitOnFailure(hr, "Failed to start display.");
135
136 hr = LoadStart(sczThemeFile, sczWxlFile, hWnd, &hLoadThread);
137 ExitOnFailure(hr, "Failed to start load.");
138
139 // message pump
140 while (0 != (fRet = ::GetMessageW(&msg, NULL, 0, 0)))
141 {
142 if (-1 == fRet)
143 {
144 hr = E_UNEXPECTED;
145 ExitOnFailure(hr, "Unexpected return value from message pump.");
146 }
147 else if (!ThemeHandleKeyboardMessage(vpTheme, msg.hwnd, &msg))
148 {
149 ::TranslateMessage(&msg);
150 ::DispatchMessageW(&msg);
151 }
152 }
153
154LExit:
155 if (::IsWindow(hWnd))
156 {
157 ::DestroyWindow(hWnd);
158 }
159
160 if (hDisplayThread)
161 {
162 ::PostThreadMessageW(vdwDisplayThreadId, WM_QUIT, 0, 0);
163 ::WaitForSingleObject(hDisplayThread, 10000);
164 ::CloseHandle(hDisplayThread);
165 }
166
167 // TODO: come up with a good way to kill the load thread, probably need to switch
168 // the ReadDirectoryW() to overlapped mode.
169 ReleaseHandle(hLoadThread);
170
171 if (atom && !::UnregisterClassW(reinterpret_cast<LPCWSTR>(atom), hInstance))
172 {
173 DWORD er = ::GetLastError();
174 er = er;
175 }
176
177 ThemeFree(vpTheme);
178 ThemeUninitialize();
179 DutilUninitialize();
180
181 // uninitialize COM
182 if (fComInitialized)
183 {
184 ::CoUninitialize();
185 }
186
187 ReleaseNullStr(vsczThemeLoadErrors);
188 ReleaseStr(sczThemeFile);
189 ReleaseStr(sczWxlFile);
190 return hr;
191}
192
193static void CALLBACK ThmviewerTraceError(
194 __in_z LPCSTR /*szFile*/,
195 __in int /*iLine*/,
196 __in REPORT_LEVEL /*rl*/,
197 __in UINT source,
198 __in HRESULT hrError,
199 __in_z __format_string LPCSTR szFormat,
200 __in va_list args
201 )
202{
203 HRESULT hr = S_OK;
204 LPSTR sczFormattedAnsi = NULL;
205 LPWSTR sczMessage = NULL;
206
207 if (DUTIL_SOURCE_THMUTIL != source)
208 {
209 ExitFunction();
210 }
211
212 hr = StrAnsiAllocFormattedArgs(&sczFormattedAnsi, szFormat, args);
213 ExitOnFailure(hr, "Failed to format error log string.");
214
215 hr = StrAllocFormatted(&sczMessage, L"Error 0x%08x: %S\r\n", hrError, sczFormattedAnsi);
216 ExitOnFailure(hr, "Failed to prepend error number to error log string.");
217
218 hr = StrAllocConcat(&vsczThemeLoadErrors, sczMessage, 0);
219 ExitOnFailure(hr, "Failed to append theme load error.");
220
221LExit:
222 ReleaseStr(sczFormattedAnsi);
223 ReleaseStr(sczMessage);
224}
225
226
227//
228// ProcessCommandLine - process the provided command line arguments.
229//
230static HRESULT ProcessCommandLine(
231 __in_z_opt LPCWSTR wzCommandLine,
232 __out_z LPWSTR* psczThemeFile,
233 __out_z LPWSTR* psczWxlFile
234 )
235{
236 HRESULT hr = S_OK;
237 int argc = 0;
238 LPWSTR* argv = NULL;
239
240 if (wzCommandLine && *wzCommandLine)
241 {
242 hr = AppParseCommandLine(wzCommandLine, &argc, &argv);
243 ExitOnFailure(hr, "Failed to parse command line.");
244
245 for (int i = 0; i < argc; ++i)
246 {
247 if (argv[i][0] == L'-' || argv[i][0] == L'/')
248 {
249 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, &argv[i][1], -1, L"lang", -1))
250 {
251 if (i + 1 >= argc)
252 {
253 ExitOnRootFailure(hr = E_INVALIDARG, "Must specify a language.");
254 }
255
256 ++i;
257 }
258 }
259 else
260 {
261 LPCWSTR wzExtension = PathExtension(argv[i]);
262 if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, NORM_IGNORECASE, wzExtension, -1, L".wxl", -1))
263 {
264 hr = StrAllocString(psczWxlFile, argv[i], 0);
265 }
266 else
267 {
268 hr = StrAllocString(psczThemeFile, argv[i], 0);
269 }
270 ExitOnFailure(hr, "Failed to copy path to file.");
271 }
272 }
273 }
274
275LExit:
276 if (argv)
277 {
278 AppFreeCommandLineArgs(argv);
279 }
280
281 return hr;
282}
283
284static HRESULT CreateTheme(
285 __in HINSTANCE hInstance,
286 __out THEME** ppTheme
287 )
288{
289 HRESULT hr = S_OK;
290
291 hr = ThemeInitialize(hInstance);
292 ExitOnFailure(hr, "Failed to initialize theme manager.");
293
294 hr = ThemeLoadFromResource(hInstance, MAKEINTRESOURCEA(THMVWR_RES_THEME_FILE), ppTheme);
295 ExitOnFailure(hr, "Failed to load theme from thmviewer.thm.");
296
297LExit:
298 return hr;
299}
300
301static HRESULT CreateMainWindowClass(
302 __in HINSTANCE hInstance,
303 __in THEME* pTheme,
304 __out ATOM* pAtom
305 )
306{
307 HRESULT hr = S_OK;
308 ATOM atom = 0;
309 WNDCLASSW wc = { };
310
311 wc.lpfnWndProc = MainWndProc;
312 wc.hInstance = hInstance;
313 wc.hIcon = reinterpret_cast<HICON>(pTheme->hIcon);
314 wc.hCursor = ::LoadCursorW(NULL, (LPCWSTR)IDC_ARROW);
315 wc.hbrBackground = pTheme->rgFonts[pTheme->dwFontId].hBackground;
316 wc.lpszMenuName = NULL;
317 wc.lpszClassName = THMVWR_WINDOW_CLASS_MAIN;
318 atom = ::RegisterClassW(&wc);
319 if (!atom)
320 {
321 ExitWithLastError(hr, "Failed to register main windowclass .");
322 }
323
324 *pAtom = atom;
325
326LExit:
327 return hr;
328}
329
330static LRESULT CALLBACK MainWndProc(
331 __in HWND hWnd,
332 __in UINT uMsg,
333 __in WPARAM wParam,
334 __in LPARAM lParam
335 )
336{
337 HANDLE_THEME* pHandleTheme = reinterpret_cast<HANDLE_THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
338
339 switch (uMsg)
340 {
341 case WM_NCCREATE:
342 {
343 //LPCREATESTRUCT lpcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
344 //pBA = reinterpret_cast<CWixStandardBootstrapperApplication*>(lpcs->lpCreateParams);
345 //::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pBA));
346 }
347 break;
348
349 case WM_NCDESTROY:
350 DecrementHandleTheme(pHandleTheme);
351 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, 0);
352 break;
353
354 case WM_CREATE:
355 {
356 HRESULT hr = ThemeLoadControls(vpTheme, vrgInitControls, countof(vrgInitControls));
357 if (FAILED(hr))
358 {
359 return -1;
360 }
361 }
362 break;
363
364 case WM_THMVWR_THEME_LOAD_BEGIN:
365 OnThemeLoadBegin(vsczThemeLoadErrors);
366 return 0;
367
368 case WM_THMVWR_THEME_LOAD_ERROR:
369 OnThemeLoadError(vpTheme, lParam);
370 return 0;
371
372 case WM_THMVWR_NEW_THEME:
373 OnNewTheme(vpTheme, hWnd, reinterpret_cast<HANDLE_THEME*>(lParam));
374 return 0;
375
376 case WM_DESTROY:
377 ::PostQuitMessage(0);
378 break;
379
380 case WM_NOTIFY:
381 {
382 NMHDR* pnmhdr = reinterpret_cast<NMHDR*>(lParam);
383 switch (pnmhdr->code)
384 {
385 case TVN_SELCHANGEDW:
386 {
387 NMTREEVIEWW* ptv = reinterpret_cast<NMTREEVIEWW*>(lParam);
388 ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_SHOWPAGE, SW_HIDE, ptv->itemOld.lParam);
389 ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_SHOWPAGE, SW_SHOW, ptv->itemNew.lParam);
390 }
391 break;
392
393 //case NM_DBLCLK:
394 // TVITEM item = { };
395 // item.mask = TVIF_PARAM;
396 // item.hItem = TreeView_GetSelection(pnmhdr->hwndFrom);
397 // TreeView_GetItem(pnmhdr->hwndFrom, &item);
398 // ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_SHOWPAGE, SW_SHOW, item.lParam);
399 // return 1;
400 }
401 }
402 break;
403 }
404
405 return ThemeDefWindowProc(vpTheme, hWnd, uMsg, wParam, lParam);
406}
407
408static void OnThemeLoadBegin(
409 __in_z_opt LPWSTR sczThemeLoadErrors
410 )
411{
412 ReleaseNullStr(sczThemeLoadErrors);
413}
414
415static void OnThemeLoadError(
416 __in THEME* pTheme,
417 __in HRESULT hrFailure
418 )
419{
420 HRESULT hr = S_OK;
421 LPWSTR sczMessage = NULL;
422 LPWSTR* psczErrors = NULL;
423 UINT cErrors = 0;
424 TVINSERTSTRUCTW tvi = { };
425
426 // Add the application node.
427 tvi.hParent = NULL;
428 tvi.hInsertAfter = TVI_ROOT;
429 tvi.item.mask = TVIF_TEXT | TVIF_PARAM;
430 tvi.item.lParam = 0;
431 tvi.item.pszText = L"Failed to load theme.";
432 tvi.hParent = reinterpret_cast<HTREEITEM>(ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)));
433
434 if (!vsczThemeLoadErrors)
435 {
436 hr = StrAllocFormatted(&sczMessage, L"Error 0x%08x.", hrFailure);
437 ExitOnFailure(hr, "Failed to format error message.");
438
439 tvi.item.pszText = sczMessage;
440 ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi));
441
442 hr = StrAllocFromError(&sczMessage, hrFailure, NULL);
443 ExitOnFailure(hr, "Failed to format error message text.");
444
445 tvi.item.pszText = sczMessage;
446 ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi));
447 }
448 else
449 {
450 hr = StrSplitAllocArray(&psczErrors, &cErrors, vsczThemeLoadErrors, L"\r\n");
451 ExitOnFailure(hr, "Failed to split theme load errors.");
452
453 for (DWORD i = 0; i < cErrors; ++i)
454 {
455 tvi.item.pszText = psczErrors[i];
456 ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi));
457 }
458 }
459
460 ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_EXPAND, TVE_EXPAND, reinterpret_cast<LPARAM>(tvi.hParent));
461
462LExit:
463 ReleaseStr(sczMessage);
464 ReleaseMem(psczErrors);
465}
466
467
468static void OnNewTheme(
469 __in THEME* pTheme,
470 __in HWND hWnd,
471 __in HANDLE_THEME* pHandle
472 )
473{
474 HANDLE_THEME* pOldHandle = reinterpret_cast<HANDLE_THEME*>(::GetWindowLongPtrW(hWnd, GWLP_USERDATA));
475 THEME* pNewTheme = pHandle->pTheme;
476
477 WCHAR wzSelectedPage[MAX_PATH] = { };
478 HTREEITEM htiSelected = NULL;
479 TVINSERTSTRUCTW tvi = { };
480 TVITEMW item = { };
481
482 if (pOldHandle)
483 {
484 DecrementHandleTheme(pOldHandle);
485 pOldHandle = NULL;
486 }
487
488 // Pass the new theme handle to the display thread so it can get the display window prepared
489 // to show the new theme.
490 IncrementHandleTheme(pHandle);
491 ::PostThreadMessageW(vdwDisplayThreadId, WM_THMVWR_NEW_THEME, 0, reinterpret_cast<LPARAM>(pHandle));
492
493 ::SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pHandle));
494
495 // Remember the currently selected item by name so we can try to automatically select it later.
496 // Otherwise, the user would see their window destroyed after every save of their theme file and
497 // have to click to get the window back.
498 item.mask = TVIF_TEXT;
499 item.pszText = wzSelectedPage;
500 item.cchTextMax = countof(wzSelectedPage);
501 item.hItem = reinterpret_cast<HTREEITEM>(ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_GETNEXTITEM, TVGN_CARET, NULL));
502 ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_GETITEM, 0, reinterpret_cast<LPARAM>(&item));
503
504 // Remove the previous items in the tree.
505 ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_DELETEITEM, 0, reinterpret_cast<LPARAM>(TVI_ROOT));
506
507 // Add the application node.
508 tvi.hParent = NULL;
509 tvi.hInsertAfter = TVI_ROOT;
510 tvi.item.mask = TVIF_TEXT | TVIF_PARAM;
511 tvi.item.lParam = 0;
512 tvi.item.pszText = pHandle && pHandle->pTheme && pHandle->pTheme->sczCaption ? pHandle->pTheme->sczCaption : L"Window";
513
514 // Add the pages.
515 tvi.hParent = reinterpret_cast<HTREEITEM>(ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)));
516 tvi.hInsertAfter = TVI_SORT;
517 for (DWORD i = 0; i < pNewTheme->cPages; ++i)
518 {
519 THEME_PAGE* pPage = pNewTheme->rgPages + i;
520 if (pPage->sczName && *pPage->sczName)
521 {
522 tvi.item.pszText = pPage->sczName;
523 tvi.item.lParam = i + 1; //prgdwPageIds[i]; - TODO: do the right thing here by calling ThemeGetPageIds(), should not assume we know how the page ids will be calculated.
524
525 HTREEITEM hti = reinterpret_cast<HTREEITEM>(ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_INSERTITEMW, 0, reinterpret_cast<LPARAM>(&tvi)));
526 if (*wzSelectedPage && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, pPage->sczName, -1, wzSelectedPage, -1))
527 {
528 htiSelected = hti;
529 }
530 }
531 }
532
533 if (*wzSelectedPage && CSTR_EQUAL == ::CompareStringW(LOCALE_NEUTRAL, 0, L"Application", -1, wzSelectedPage, -1))
534 {
535 htiSelected = tvi.hParent;
536 }
537
538 ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_EXPAND, TVE_EXPAND, reinterpret_cast<LPARAM>(tvi.hParent));
539 if (htiSelected)
540 {
541 ThemeSendControlMessage(pTheme, THMVWR_CONTROL_TREE, TVM_SELECTITEM, TVGN_CARET, reinterpret_cast<LPARAM>(htiSelected));
542 }
543}
diff --git a/src/samples/thmviewer/thmviewer.manifest b/src/samples/thmviewer/thmviewer.manifest
new file mode 100644
index 00000000..4663b61c
--- /dev/null
+++ b/src/samples/thmviewer/thmviewer.manifest
@@ -0,0 +1,19 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4
5<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
6 <assemblyIdentity name="thmviewer.exe" version="1.0.0.0" processorArchitecture="x86" type="win32"/>
7 <description>WiX Toolset Theme Viewer</description>
8 <dependency><dependentAssembly><assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="X86" publicKeyToken="6595b64144ccf1df" language="*" /></dependentAssembly></dependency>
9 <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"><application><supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/><supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/></application></compatibility>
10 <application xmlns="urn:schemas-microsoft-com:asm.v3">
11 <windowsSettings xmlns="urn:schemas-microsoft-com:asm.v3">
12 <!-- pre-Win10 1607 -->
13 <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
14 <!-- Win10 picks the first one it recognizes -->
15 <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor, System</dpiAwareness>
16 </windowsSettings>
17 </application>
18 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"><security><requestedPrivileges><requestedExecutionLevel level="asInvoker" uiAccess="false"/></requestedPrivileges></security></trustInfo>
19</assembly>
diff --git a/src/samples/thmviewer/thmviewer.rc b/src/samples/thmviewer/thmviewer.rc
new file mode 100644
index 00000000..dc6d7242
--- /dev/null
+++ b/src/samples/thmviewer/thmviewer.rc
@@ -0,0 +1,12 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3#include <winver.h>
4#include <windows.h>
5#include "resource.h"
6
7//#define MANIFEST_RESOURCE_ID 1
8//MANIFEST_RESOURCE_ID RT_MANIFEST "thmviewer.manifest"
9
10LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
11THMVWR_RES_THEME_FILE RCDATA "Resources\\thm.xml"
12THMVWR_RES_RICHEDIT_FILE RCDATA "Resources\\LoremIpsum.rtf"
diff --git a/src/samples/thmviewer/thmviewer.v3.ncrunchproject b/src/samples/thmviewer/thmviewer.v3.ncrunchproject
new file mode 100644
index 00000000..3cffd6ce
--- /dev/null
+++ b/src/samples/thmviewer/thmviewer.v3.ncrunchproject
@@ -0,0 +1,7 @@
1<ProjectConfiguration>
2 <Settings>
3 <AdditionalFilesToIncludeForProject>
4 <Value>thmviewer.manifest</Value>
5 </AdditionalFilesToIncludeForProject>
6 </Settings>
7</ProjectConfiguration> \ No newline at end of file
diff --git a/src/samples/thmviewer/thmviewer.vcxproj b/src/samples/thmviewer/thmviewer.vcxproj
new file mode 100644
index 00000000..c02dc237
--- /dev/null
+++ b/src/samples/thmviewer/thmviewer.vcxproj
@@ -0,0 +1,88 @@
1<?xml version="1.0" encoding="utf-8"?>
2<!-- Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. -->
3
4<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
5 <Import Project="..\..\packages\Microsoft.SourceLink.GitHub.1.0.0\build\Microsoft.SourceLink.GitHub.props" Condition="Exists('..\..\packages\Microsoft.SourceLink.GitHub.1.0.0\build\Microsoft.SourceLink.GitHub.props')" />
6 <Import Project="..\..\packages\Microsoft.SourceLink.Common.1.0.0\build\Microsoft.SourceLink.Common.props" Condition="Exists('..\..\packages\Microsoft.SourceLink.Common.1.0.0\build\Microsoft.SourceLink.Common.props')" />
7 <Import Project="..\..\packages\Microsoft.Build.Tasks.Git.1.0.0\build\Microsoft.Build.Tasks.Git.props" Condition="Exists('..\..\packages\Microsoft.Build.Tasks.Git.1.0.0\build\Microsoft.Build.Tasks.Git.props')" />
8 <Import Project="..\..\packages\WixToolset.DUtil.4.0.72\build\WixToolset.DUtil.props" Condition="Exists('..\..\packages\WixToolset.DUtil.4.0.72\build\WixToolset.DUtil.props')" />
9
10 <ItemGroup Label="ProjectConfigurations">
11 <ProjectConfiguration Include="Debug|Win32">
12 <Configuration>Debug</Configuration>
13 <Platform>Win32</Platform>
14 </ProjectConfiguration>
15 <ProjectConfiguration Include="Release|Win32">
16 <Configuration>Release</Configuration>
17 <Platform>Win32</Platform>
18 </ProjectConfiguration>
19 </ItemGroup>
20
21 <PropertyGroup Label="Globals">
22 <ProjectGuid>{95228C13-97F5-484A-B4A2-ECF4618B0881}</ProjectGuid>
23 <Keyword>Win32Proj</Keyword>
24 <ConfigurationType>Application</ConfigurationType>
25 <PlatformToolset>v142</PlatformToolset>
26 <CharacterSet>Unicode</CharacterSet>
27 <Description>WiX Toolset Theme Viewer</Description>
28 </PropertyGroup>
29
30 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
31 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
32
33 <ImportGroup Label="ExtensionSettings">
34 </ImportGroup>
35
36 <ImportGroup Label="Shared">
37 </ImportGroup>
38
39 <PropertyGroup>
40 <ProjectAdditionalLinkLibraries>comctl32.lib;gdiplus.lib;msimg32.lib;shlwapi.lib</ProjectAdditionalLinkLibraries>
41 </PropertyGroup>
42
43 <ItemGroup>
44 <ClCompile Include="display.cpp" />
45 <ClCompile Include="load.cpp" />
46 <ClCompile Include="precomp.cpp">
47 <PrecompiledHeader>Create</PrecompiledHeader>
48 </ClCompile>
49 <ClCompile Include="thmviewer.cpp" />
50 </ItemGroup>
51 <ItemGroup>
52 <ClInclude Include="precomp.h" />
53 <ClInclude Include="resource.h" />
54 </ItemGroup>
55 <ItemGroup>
56 <None Include="packages.config" />
57 <None Include="Resources\LoremIpsum.rtf" />
58 <None Include="Resources\thm.xml" />
59 </ItemGroup>
60 <ItemGroup>
61 <ResourceCompile Include="thmviewer.rc" />
62 </ItemGroup>
63 <ItemGroup>
64 <Manifest Include="thmviewer.manifest" />
65 </ItemGroup>
66
67 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
68 <ImportGroup Label="ExtensionTargets">
69 <Import Project="..\..\packages\Microsoft.Build.Tasks.Git.1.0.0\build\Microsoft.Build.Tasks.Git.targets" Condition="Exists('..\..\packages\Microsoft.Build.Tasks.Git.1.0.0\build\Microsoft.Build.Tasks.Git.targets')" />
70 <Import Project="..\..\packages\Microsoft.SourceLink.Common.1.0.0\build\Microsoft.SourceLink.Common.targets" Condition="Exists('..\..\packages\Microsoft.SourceLink.Common.1.0.0\build\Microsoft.SourceLink.Common.targets')" />
71 <Import Project="..\..\packages\Microsoft.SourceLink.GitHub.1.0.0\build\Microsoft.SourceLink.GitHub.targets" Condition="Exists('..\..\packages\Microsoft.SourceLink.GitHub.1.0.0\build\Microsoft.SourceLink.GitHub.targets')" />
72 <Import Project="..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets" Condition="Exists('..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" />
73 </ImportGroup>
74
75 <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
76 <PropertyGroup>
77 <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
78 </PropertyGroup>
79 <Error Condition="!Exists('..\..\packages\Microsoft.Build.Tasks.Git.1.0.0\build\Microsoft.Build.Tasks.Git.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Build.Tasks.Git.1.0.0\build\Microsoft.Build.Tasks.Git.props'))" />
80 <Error Condition="!Exists('..\..\packages\Microsoft.Build.Tasks.Git.1.0.0\build\Microsoft.Build.Tasks.Git.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.Build.Tasks.Git.1.0.0\build\Microsoft.Build.Tasks.Git.targets'))" />
81 <Error Condition="!Exists('..\..\packages\Microsoft.SourceLink.Common.1.0.0\build\Microsoft.SourceLink.Common.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.SourceLink.Common.1.0.0\build\Microsoft.SourceLink.Common.props'))" />
82 <Error Condition="!Exists('..\..\packages\Microsoft.SourceLink.Common.1.0.0\build\Microsoft.SourceLink.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.SourceLink.Common.1.0.0\build\Microsoft.SourceLink.Common.targets'))" />
83 <Error Condition="!Exists('..\..\packages\Microsoft.SourceLink.GitHub.1.0.0\build\Microsoft.SourceLink.GitHub.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.SourceLink.GitHub.1.0.0\build\Microsoft.SourceLink.GitHub.props'))" />
84 <Error Condition="!Exists('..\..\packages\Microsoft.SourceLink.GitHub.1.0.0\build\Microsoft.SourceLink.GitHub.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Microsoft.SourceLink.GitHub.1.0.0\build\Microsoft.SourceLink.GitHub.targets'))" />
85 <Error Condition="!Exists('..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Nerdbank.GitVersioning.3.3.37\build\Nerdbank.GitVersioning.targets'))" />
86 <Error Condition="!Exists('..\..\packages\WixToolset.DUtil.4.0.72\build\WixToolset.DUtil.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\WixToolset.DUtil.4.0.72\build\WixToolset.DUtil.props'))" />
87 </Target>
88</Project> \ No newline at end of file
diff --git a/src/samples/thmviewer/thmviewer.vcxproj.filters b/src/samples/thmviewer/thmviewer.vcxproj.filters
new file mode 100644
index 00000000..488d5510
--- /dev/null
+++ b/src/samples/thmviewer/thmviewer.vcxproj.filters
@@ -0,0 +1,56 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 <ItemGroup>
4 <Filter Include="Source Files">
5 <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
6 <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
7 </Filter>
8 <Filter Include="Header Files">
9 <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
10 <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
11 </Filter>
12 <Filter Include="Resource Files">
13 <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
14 <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav</Extensions>
15 </Filter>
16 </ItemGroup>
17 <ItemGroup>
18 <ClCompile Include="thmviewer.cpp">
19 <Filter>Source Files</Filter>
20 </ClCompile>
21 <ClCompile Include="display.cpp">
22 <Filter>Source Files</Filter>
23 </ClCompile>
24 <ClCompile Include="load.cpp">
25 <Filter>Source Files</Filter>
26 </ClCompile>
27 <ClCompile Include="precomp.cpp">
28 <Filter>Source Files</Filter>
29 </ClCompile>
30 </ItemGroup>
31 <ItemGroup>
32 <None Include="Resources\thm.xml">
33 <Filter>Resource Files</Filter>
34 </None>
35 <None Include="Resources\LoremIpsum.rtf">
36 <Filter>Resource Files</Filter>
37 </None>
38 <None Include="packages.config" />
39 </ItemGroup>
40 <ItemGroup>
41 <ClInclude Include="precomp.h">
42 <Filter>Header Files</Filter>
43 </ClInclude>
44 <ClInclude Include="resource.h">
45 <Filter>Header Files</Filter>
46 </ClInclude>
47 </ItemGroup>
48 <ItemGroup>
49 <ResourceCompile Include="thmviewer.rc">
50 <Filter>Resource Files</Filter>
51 </ResourceCompile>
52 </ItemGroup>
53 <ItemGroup>
54 <Manifest Include="thmviewer.manifest" />
55 </ItemGroup>
56</Project> \ No newline at end of file