diff options
20 files changed, 2636 insertions, 0 deletions
diff --git a/src/WixToolset.WixBA/BrowserProperties.cs b/src/WixToolset.WixBA/BrowserProperties.cs new file mode 100644 index 00000000..f40d4ed9 --- /dev/null +++ b/src/WixToolset.WixBA/BrowserProperties.cs | |||
@@ -0,0 +1,40 @@ | |||
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 | namespace WixToolset.UX | ||
4 | { | ||
5 | using System.Windows; | ||
6 | using System.Windows.Controls; | ||
7 | |||
8 | /// <summary> | ||
9 | /// Dependency Properties to support using a WebBrowser object. | ||
10 | /// </summary> | ||
11 | class BrowserProperties | ||
12 | { | ||
13 | /// <summary> | ||
14 | /// Dependency Propery used to pass an HTML string to the webBrowser object. | ||
15 | /// </summary> | ||
16 | public static readonly DependencyProperty HtmlDocProperty = | ||
17 | DependencyProperty.RegisterAttached("HtmlDoc", typeof(string), typeof(BrowserProperties), new PropertyMetadata(OnHtmlDocChanged)); | ||
18 | |||
19 | public static string GetHtmlDoc(DependencyObject dependencyObject) | ||
20 | { | ||
21 | return (string)dependencyObject.GetValue(HtmlDocProperty); | ||
22 | } | ||
23 | |||
24 | public static void SetHtmlDoc(DependencyObject dependencyObject, string htmldoc) | ||
25 | { | ||
26 | dependencyObject.SetValue(HtmlDocProperty, htmldoc); | ||
27 | } | ||
28 | |||
29 | /// <summary> | ||
30 | /// Event handler that passes the HtmlDoc Dependency Property to MavigateToString method. | ||
31 | /// </summary> | ||
32 | /// <param name="d"></param> | ||
33 | /// <param name="e"></param> | ||
34 | private static void OnHtmlDocChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) | ||
35 | { | ||
36 | var webBrowser = (WebBrowser)d; | ||
37 | webBrowser.NavigateToString((string)e.NewValue); | ||
38 | } | ||
39 | } | ||
40 | } | ||
diff --git a/src/WixToolset.WixBA/Hresult.cs b/src/WixToolset.WixBA/Hresult.cs new file mode 100644 index 00000000..68b4c5ea --- /dev/null +++ b/src/WixToolset.WixBA/Hresult.cs | |||
@@ -0,0 +1,22 @@ | |||
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 | namespace WixToolset.UX | ||
4 | { | ||
5 | using System; | ||
6 | |||
7 | /// <summary> | ||
8 | /// Utility class to work with HRESULTs | ||
9 | /// </summary> | ||
10 | internal class Hresult | ||
11 | { | ||
12 | /// <summary> | ||
13 | /// Determines if an HRESULT was a success code or not. | ||
14 | /// </summary> | ||
15 | /// <param name="status">HRESULT to verify.</param> | ||
16 | /// <returns>True if the status is a success code.</returns> | ||
17 | public static bool Succeeded(int status) | ||
18 | { | ||
19 | return status >= 0; | ||
20 | } | ||
21 | } | ||
22 | } | ||
diff --git a/src/WixToolset.WixBA/InstallationViewModel.cs b/src/WixToolset.WixBA/InstallationViewModel.cs new file mode 100644 index 00000000..6bec427a --- /dev/null +++ b/src/WixToolset.WixBA/InstallationViewModel.cs | |||
@@ -0,0 +1,670 @@ | |||
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 | namespace WixToolset.UX | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.ComponentModel; | ||
8 | using System.Linq; | ||
9 | using System.Reflection; | ||
10 | using System.Windows; | ||
11 | using System.Windows.Input; | ||
12 | using IO = System.IO; | ||
13 | using WixToolset.Bootstrapper; | ||
14 | |||
15 | /// <summary> | ||
16 | /// The states of detection. | ||
17 | /// </summary> | ||
18 | public enum DetectionState | ||
19 | { | ||
20 | Absent, | ||
21 | Present, | ||
22 | Newer, | ||
23 | } | ||
24 | |||
25 | /// <summary> | ||
26 | /// The states of installation. | ||
27 | /// </summary> | ||
28 | public enum InstallationState | ||
29 | { | ||
30 | Initializing, | ||
31 | Detecting, | ||
32 | Waiting, | ||
33 | Planning, | ||
34 | Applying, | ||
35 | Applied, | ||
36 | Failed, | ||
37 | } | ||
38 | |||
39 | /// <summary> | ||
40 | /// The model of the installation view in WixBA. | ||
41 | /// </summary> | ||
42 | public class InstallationViewModel : PropertyNotifyBase | ||
43 | { | ||
44 | private RootViewModel root; | ||
45 | |||
46 | private Dictionary<string, int> downloadRetries; | ||
47 | private bool downgrade; | ||
48 | private string downgradeMessage; | ||
49 | |||
50 | private ICommand licenseCommand; | ||
51 | private ICommand launchHomePageCommand; | ||
52 | private ICommand launchNewsCommand; | ||
53 | private ICommand launchVSExtensionPageCommand; | ||
54 | private ICommand installCommand; | ||
55 | private ICommand repairCommand; | ||
56 | private ICommand uninstallCommand; | ||
57 | private ICommand openLogCommand; | ||
58 | private ICommand openLogFolderCommand; | ||
59 | private ICommand tryAgainCommand; | ||
60 | |||
61 | private string message; | ||
62 | private DateTime cachePackageStart; | ||
63 | private DateTime executePackageStart; | ||
64 | |||
65 | /// <summary> | ||
66 | /// Creates a new model of the installation view. | ||
67 | /// </summary> | ||
68 | public InstallationViewModel(RootViewModel root) | ||
69 | { | ||
70 | this.root = root; | ||
71 | this.downloadRetries = new Dictionary<string, int>(); | ||
72 | |||
73 | this.root.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(this.RootPropertyChanged); | ||
74 | |||
75 | WixBA.Model.Bootstrapper.DetectBegin += this.DetectBegin; | ||
76 | WixBA.Model.Bootstrapper.DetectRelatedBundle += this.DetectedRelatedBundle; | ||
77 | WixBA.Model.Bootstrapper.DetectComplete += this.DetectComplete; | ||
78 | WixBA.Model.Bootstrapper.PlanPackageBegin += this.PlanPackageBegin; | ||
79 | WixBA.Model.Bootstrapper.PlanComplete += this.PlanComplete; | ||
80 | WixBA.Model.Bootstrapper.ApplyBegin += this.ApplyBegin; | ||
81 | WixBA.Model.Bootstrapper.CacheAcquireBegin += this.CacheAcquireBegin; | ||
82 | WixBA.Model.Bootstrapper.CacheAcquireComplete += this.CacheAcquireComplete; | ||
83 | WixBA.Model.Bootstrapper.ExecutePackageBegin += this.ExecutePackageBegin; | ||
84 | WixBA.Model.Bootstrapper.ExecutePackageComplete += this.ExecutePackageComplete; | ||
85 | WixBA.Model.Bootstrapper.Error += this.ExecuteError; | ||
86 | WixBA.Model.Bootstrapper.ResolveSource += this.ResolveSource; | ||
87 | WixBA.Model.Bootstrapper.ApplyComplete += this.ApplyComplete; | ||
88 | } | ||
89 | |||
90 | void RootPropertyChanged(object sender, PropertyChangedEventArgs e) | ||
91 | { | ||
92 | if (("DetectState" == e.PropertyName) || ("InstallState" == e.PropertyName)) | ||
93 | { | ||
94 | base.OnPropertyChanged("RepairEnabled"); | ||
95 | base.OnPropertyChanged("InstallEnabled"); | ||
96 | base.OnPropertyChanged("IsComplete"); | ||
97 | base.OnPropertyChanged("IsSuccessfulCompletion"); | ||
98 | base.OnPropertyChanged("IsFailedCompletion"); | ||
99 | base.OnPropertyChanged("StatusText"); | ||
100 | base.OnPropertyChanged("UninstallEnabled"); | ||
101 | } | ||
102 | } | ||
103 | |||
104 | /// <summary> | ||
105 | /// Gets the version for the application. | ||
106 | /// </summary> | ||
107 | public string Version | ||
108 | { | ||
109 | get { return String.Concat("v", WixBA.Model.Version.ToString()); } | ||
110 | } | ||
111 | |||
112 | /// <summary> | ||
113 | /// The Publisher of this bundle. | ||
114 | /// </summary> | ||
115 | public string Publisher | ||
116 | { | ||
117 | get | ||
118 | { | ||
119 | string company = "[AssemblyCompany]"; | ||
120 | return WixDistribution.ReplacePlaceholders(company, typeof(WixBA).Assembly); | ||
121 | } | ||
122 | } | ||
123 | |||
124 | /// <summary> | ||
125 | /// The Publisher of this bundle. | ||
126 | /// </summary> | ||
127 | public string SupportUrl | ||
128 | { | ||
129 | get | ||
130 | { | ||
131 | return WixDistribution.SupportUrl; | ||
132 | } | ||
133 | } | ||
134 | public string VSExtensionUrl | ||
135 | { | ||
136 | get | ||
137 | { | ||
138 | return WixDistribution.VSExtensionsLandingUrl; | ||
139 | } | ||
140 | } | ||
141 | |||
142 | public string Message | ||
143 | { | ||
144 | get | ||
145 | { | ||
146 | return this.message; | ||
147 | } | ||
148 | |||
149 | set | ||
150 | { | ||
151 | if (this.message != value) | ||
152 | { | ||
153 | this.message = value; | ||
154 | base.OnPropertyChanged("Message"); | ||
155 | } | ||
156 | } | ||
157 | } | ||
158 | |||
159 | /// <summary> | ||
160 | /// Gets and sets whether the view model considers this install to be a downgrade. | ||
161 | /// </summary> | ||
162 | public bool Downgrade | ||
163 | { | ||
164 | get | ||
165 | { | ||
166 | return this.downgrade; | ||
167 | } | ||
168 | |||
169 | set | ||
170 | { | ||
171 | if (this.downgrade != value) | ||
172 | { | ||
173 | this.downgrade = value; | ||
174 | base.OnPropertyChanged("Downgrade"); | ||
175 | } | ||
176 | } | ||
177 | } | ||
178 | |||
179 | public string DowngradeMessage | ||
180 | { | ||
181 | get | ||
182 | { | ||
183 | return this.downgradeMessage; | ||
184 | } | ||
185 | set | ||
186 | { | ||
187 | if (this.downgradeMessage != value) | ||
188 | { | ||
189 | this.downgradeMessage = value; | ||
190 | base.OnPropertyChanged("DowngradeMessage"); | ||
191 | } | ||
192 | } | ||
193 | } | ||
194 | |||
195 | public ICommand LaunchHomePageCommand | ||
196 | { | ||
197 | get | ||
198 | { | ||
199 | if (this.launchHomePageCommand == null) | ||
200 | { | ||
201 | this.launchHomePageCommand = new RelayCommand(param => WixBA.LaunchUrl(this.SupportUrl), param => true); | ||
202 | } | ||
203 | |||
204 | return this.launchHomePageCommand; | ||
205 | } | ||
206 | } | ||
207 | |||
208 | public ICommand LaunchNewsCommand | ||
209 | { | ||
210 | get | ||
211 | { | ||
212 | if (this.launchNewsCommand == null) | ||
213 | { | ||
214 | this.launchNewsCommand = new RelayCommand(param => WixBA.LaunchUrl(WixDistribution.NewsUrl), param => true); | ||
215 | } | ||
216 | |||
217 | return this.launchNewsCommand; | ||
218 | } | ||
219 | } | ||
220 | |||
221 | public ICommand LaunchVSExtensionPageCommand | ||
222 | { | ||
223 | get | ||
224 | { | ||
225 | if (this.launchVSExtensionPageCommand == null) | ||
226 | { | ||
227 | this.launchVSExtensionPageCommand = new RelayCommand(param => WixBA.LaunchUrl(WixDistribution.VSExtensionsLandingUrl), param => true); | ||
228 | } | ||
229 | |||
230 | return this.launchVSExtensionPageCommand; | ||
231 | } | ||
232 | } | ||
233 | |||
234 | public ICommand LicenseCommand | ||
235 | { | ||
236 | get | ||
237 | { | ||
238 | if (this.licenseCommand == null) | ||
239 | { | ||
240 | this.licenseCommand = new RelayCommand(param => this.LaunchLicense(), param => true); | ||
241 | } | ||
242 | |||
243 | return this.licenseCommand; | ||
244 | } | ||
245 | } | ||
246 | |||
247 | public bool LicenseEnabled | ||
248 | { | ||
249 | get { return this.LicenseCommand.CanExecute(this); } | ||
250 | } | ||
251 | |||
252 | public ICommand CloseCommand | ||
253 | { | ||
254 | get { return this.root.CloseCommand; } | ||
255 | } | ||
256 | |||
257 | public bool IsComplete | ||
258 | { | ||
259 | get { return IsSuccessfulCompletion || IsFailedCompletion; } | ||
260 | } | ||
261 | |||
262 | public bool IsSuccessfulCompletion | ||
263 | { | ||
264 | get { return InstallationState.Applied == this.root.InstallState; } | ||
265 | } | ||
266 | |||
267 | public bool IsFailedCompletion | ||
268 | { | ||
269 | get { return InstallationState.Failed == this.root.InstallState; } | ||
270 | } | ||
271 | |||
272 | public ICommand InstallCommand | ||
273 | { | ||
274 | get | ||
275 | { | ||
276 | if (this.installCommand == null) | ||
277 | { | ||
278 | this.installCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Install), param => this.root.DetectState == DetectionState.Absent && this.root.InstallState == InstallationState.Waiting); | ||
279 | } | ||
280 | |||
281 | return this.installCommand; | ||
282 | } | ||
283 | } | ||
284 | |||
285 | public bool InstallEnabled | ||
286 | { | ||
287 | get { return this.InstallCommand.CanExecute(this); } | ||
288 | } | ||
289 | |||
290 | public ICommand RepairCommand | ||
291 | { | ||
292 | get | ||
293 | { | ||
294 | if (this.repairCommand == null) | ||
295 | { | ||
296 | this.repairCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Repair), param => this.root.DetectState == DetectionState.Present && this.root.InstallState == InstallationState.Waiting); | ||
297 | } | ||
298 | |||
299 | return this.repairCommand; | ||
300 | } | ||
301 | } | ||
302 | |||
303 | public bool RepairEnabled | ||
304 | { | ||
305 | get { return this.RepairCommand.CanExecute(this); } | ||
306 | } | ||
307 | |||
308 | public ICommand UninstallCommand | ||
309 | { | ||
310 | get | ||
311 | { | ||
312 | if (this.uninstallCommand == null) | ||
313 | { | ||
314 | this.uninstallCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.Uninstall), param => this.root.DetectState == DetectionState.Present && this.root.InstallState == InstallationState.Waiting); | ||
315 | } | ||
316 | |||
317 | return this.uninstallCommand; | ||
318 | } | ||
319 | } | ||
320 | |||
321 | public bool UninstallEnabled | ||
322 | { | ||
323 | get { return this.UninstallCommand.CanExecute(this); } | ||
324 | } | ||
325 | |||
326 | public ICommand OpenLogCommand | ||
327 | { | ||
328 | get | ||
329 | { | ||
330 | if (this.openLogCommand == null) | ||
331 | { | ||
332 | this.openLogCommand = new RelayCommand(param => WixBA.OpenLog(new Uri(WixBA.Model.Engine.StringVariables["WixBundleLog"]))); | ||
333 | } | ||
334 | return this.openLogCommand; | ||
335 | } | ||
336 | } | ||
337 | |||
338 | public ICommand OpenLogFolderCommand | ||
339 | { | ||
340 | get | ||
341 | { | ||
342 | if (this.openLogFolderCommand == null) | ||
343 | { | ||
344 | string logFolder = IO.Path.GetDirectoryName(WixBA.Model.Engine.StringVariables["WixBundleLog"]); | ||
345 | this.openLogFolderCommand = new RelayCommand(param => WixBA.OpenLogFolder(logFolder)); | ||
346 | } | ||
347 | return this.openLogFolderCommand; | ||
348 | } | ||
349 | } | ||
350 | |||
351 | public ICommand TryAgainCommand | ||
352 | { | ||
353 | get | ||
354 | { | ||
355 | if (this.tryAgainCommand == null) | ||
356 | { | ||
357 | this.tryAgainCommand = new RelayCommand(param => | ||
358 | { | ||
359 | this.root.Canceled = false; | ||
360 | WixBA.Plan(WixBA.Model.PlannedAction); | ||
361 | }, param => IsFailedCompletion); | ||
362 | } | ||
363 | |||
364 | return this.tryAgainCommand; | ||
365 | } | ||
366 | } | ||
367 | |||
368 | public string StatusText | ||
369 | { | ||
370 | get | ||
371 | { | ||
372 | switch(this.root.InstallState) | ||
373 | { | ||
374 | case InstallationState.Applied: | ||
375 | return "Complete"; | ||
376 | case InstallationState.Failed: | ||
377 | return this.root.Canceled ? "Cancelled" : "Failed"; | ||
378 | default: | ||
379 | return "Unknown"; // this shouldn't be shown in the UI. | ||
380 | } | ||
381 | } | ||
382 | } | ||
383 | |||
384 | /// <summary> | ||
385 | /// Launches the license in the default viewer. | ||
386 | /// </summary> | ||
387 | private void LaunchLicense() | ||
388 | { | ||
389 | string folder = IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); | ||
390 | WixBA.LaunchUrl(IO.Path.Combine(folder, "License.txt")); | ||
391 | } | ||
392 | |||
393 | private void DetectBegin(object sender, DetectBeginEventArgs e) | ||
394 | { | ||
395 | this.root.DetectState = e.Installed ? DetectionState.Present : DetectionState.Absent; | ||
396 | WixBA.Model.PlannedAction = LaunchAction.Unknown; | ||
397 | } | ||
398 | |||
399 | private void DetectedRelatedBundle(object sender, DetectRelatedBundleEventArgs e) | ||
400 | { | ||
401 | if (e.Operation == RelatedOperation.Downgrade) | ||
402 | { | ||
403 | this.Downgrade = true; | ||
404 | } | ||
405 | |||
406 | if (!WixBA.Model.Bootstrapper.BAManifest.Bundle.Packages.ContainsKey(e.ProductCode)) | ||
407 | { | ||
408 | WixBA.Model.Bootstrapper.BAManifest.Bundle.AddRelatedBundleAsPackage(e); | ||
409 | } | ||
410 | } | ||
411 | |||
412 | private void DetectComplete(object sender, DetectCompleteEventArgs e) | ||
413 | { | ||
414 | // Parse the command line string before any planning. | ||
415 | this.ParseCommandLine(); | ||
416 | this.root.InstallState = InstallationState.Waiting; | ||
417 | |||
418 | if (LaunchAction.Uninstall == WixBA.Model.Command.Action && | ||
419 | ResumeType.Arp != WixBA.Model.Command.Resume) // MSI and WixStdBA require some kind of confirmation before proceeding so WixBA should, too. | ||
420 | { | ||
421 | WixBA.Model.Engine.Log(LogLevel.Verbose, "Invoking automatic plan for uninstall"); | ||
422 | WixBA.Plan(LaunchAction.Uninstall); | ||
423 | } | ||
424 | else if (Hresult.Succeeded(e.Status)) | ||
425 | { | ||
426 | if (this.Downgrade) | ||
427 | { | ||
428 | this.root.DetectState = DetectionState.Newer; | ||
429 | IEnumerable<PackageInfo> relatedPackages = WixBA.Model.Bootstrapper.BAManifest.Bundle.Packages.Values.Where(p => p.Type == PackageType.UpgradeBundle); | ||
430 | Version installedVersion = relatedPackages.Any() ? new Version(relatedPackages.Max(p => p.Version)) : null; | ||
431 | if (installedVersion != null && installedVersion < new Version(4, 1) && installedVersion.Build > 10) | ||
432 | { | ||
433 | this.DowngradeMessage = "You must uninstall WiX v" + installedVersion + " before you can install this."; | ||
434 | } | ||
435 | else | ||
436 | { | ||
437 | this.DowngradeMessage = "There is already a newer version of WiX installed on this machine."; | ||
438 | } | ||
439 | } | ||
440 | |||
441 | if (LaunchAction.Layout == WixBA.Model.Command.Action) | ||
442 | { | ||
443 | WixBA.PlanLayout(); | ||
444 | } | ||
445 | else if (WixBA.Model.Command.Display != Display.Full) | ||
446 | { | ||
447 | // If we're not waiting for the user to click install, dispatch plan with the default action. | ||
448 | WixBA.Model.Engine.Log(LogLevel.Verbose, "Invoking automatic plan for non-interactive mode."); | ||
449 | WixBA.Plan(WixBA.Model.Command.Action); | ||
450 | } | ||
451 | } | ||
452 | else | ||
453 | { | ||
454 | this.root.InstallState = InstallationState.Failed; | ||
455 | } | ||
456 | |||
457 | // Force all commands to reevaluate CanExecute. | ||
458 | // InvalidateRequerySuggested must be run on the UI thread. | ||
459 | root.Dispatcher.Invoke(new Action(CommandManager.InvalidateRequerySuggested)); | ||
460 | } | ||
461 | |||
462 | private void PlanPackageBegin(object sender, PlanPackageBeginEventArgs e) | ||
463 | { | ||
464 | // If we're able to run our BA, we don't want to install the .NET Framework since the framework on the machine is already good enough. | ||
465 | if ( e.PackageId.StartsWith("NetFx4", StringComparison.OrdinalIgnoreCase)) | ||
466 | { | ||
467 | e.State = RequestState.None; | ||
468 | } | ||
469 | } | ||
470 | |||
471 | private void PlanComplete(object sender, PlanCompleteEventArgs e) | ||
472 | { | ||
473 | if (Hresult.Succeeded(e.Status)) | ||
474 | { | ||
475 | this.root.PreApplyState = this.root.InstallState; | ||
476 | this.root.InstallState = InstallationState.Applying; | ||
477 | WixBA.Model.Engine.Apply(this.root.ViewWindowHandle); | ||
478 | } | ||
479 | else | ||
480 | { | ||
481 | this.root.InstallState = InstallationState.Failed; | ||
482 | } | ||
483 | } | ||
484 | |||
485 | private void ApplyBegin(object sender, ApplyBeginEventArgs e) | ||
486 | { | ||
487 | this.downloadRetries.Clear(); | ||
488 | } | ||
489 | |||
490 | private void CacheAcquireBegin(object sender, CacheAcquireBeginEventArgs e) | ||
491 | { | ||
492 | this.cachePackageStart = DateTime.Now; | ||
493 | } | ||
494 | |||
495 | private void CacheAcquireComplete(object sender, CacheAcquireCompleteEventArgs e) | ||
496 | { | ||
497 | this.AddPackageTelemetry("Cache", e.PackageOrContainerId ?? String.Empty, DateTime.Now.Subtract(this.cachePackageStart).TotalMilliseconds, e.Status); | ||
498 | } | ||
499 | |||
500 | private void ExecutePackageBegin(object sender, ExecutePackageBeginEventArgs e) | ||
501 | { | ||
502 | lock (this) | ||
503 | { | ||
504 | this.executePackageStart = e.ShouldExecute ? DateTime.Now : DateTime.MinValue; | ||
505 | } | ||
506 | } | ||
507 | |||
508 | private void ExecutePackageComplete(object sender, ExecutePackageCompleteEventArgs e) | ||
509 | { | ||
510 | lock (this) | ||
511 | { | ||
512 | if (DateTime.MinValue < this.executePackageStart) | ||
513 | { | ||
514 | this.AddPackageTelemetry("Execute", e.PackageId ?? String.Empty, DateTime.Now.Subtract(this.executePackageStart).TotalMilliseconds, e.Status); | ||
515 | this.executePackageStart = DateTime.MinValue; | ||
516 | } | ||
517 | } | ||
518 | } | ||
519 | |||
520 | private void ExecuteError(object sender, ErrorEventArgs e) | ||
521 | { | ||
522 | lock (this) | ||
523 | { | ||
524 | if (!this.root.Canceled) | ||
525 | { | ||
526 | // If the error is a cancel coming from the engine during apply we want to go back to the preapply state. | ||
527 | if (InstallationState.Applying == this.root.InstallState && (int)Error.UserCancelled == e.ErrorCode) | ||
528 | { | ||
529 | this.root.InstallState = this.root.PreApplyState; | ||
530 | } | ||
531 | else | ||
532 | { | ||
533 | this.Message = e.ErrorMessage; | ||
534 | |||
535 | if (Display.Full == WixBA.Model.Command.Display) | ||
536 | { | ||
537 | // On HTTP authentication errors, have the engine try to do authentication for us. | ||
538 | if (ErrorType.HttpServerAuthentication == e.ErrorType || ErrorType.HttpProxyAuthentication == e.ErrorType) | ||
539 | { | ||
540 | e.Result = Result.TryAgain; | ||
541 | } | ||
542 | else // show an error dialog. | ||
543 | { | ||
544 | MessageBoxButton msgbox = MessageBoxButton.OK; | ||
545 | switch (e.UIHint & 0xF) | ||
546 | { | ||
547 | case 0: | ||
548 | msgbox = MessageBoxButton.OK; | ||
549 | break; | ||
550 | case 1: | ||
551 | msgbox = MessageBoxButton.OKCancel; | ||
552 | break; | ||
553 | // There is no 2! That would have been MB_ABORTRETRYIGNORE. | ||
554 | case 3: | ||
555 | msgbox = MessageBoxButton.YesNoCancel; | ||
556 | break; | ||
557 | case 4: | ||
558 | msgbox = MessageBoxButton.YesNo; | ||
559 | break; | ||
560 | // default: stay with MBOK since an exact match is not available. | ||
561 | } | ||
562 | |||
563 | MessageBoxResult result = MessageBoxResult.None; | ||
564 | WixBA.View.Dispatcher.Invoke((Action)delegate() | ||
565 | { | ||
566 | result = MessageBox.Show(WixBA.View, e.ErrorMessage, "WiX Toolset", msgbox, MessageBoxImage.Error); | ||
567 | } | ||
568 | ); | ||
569 | |||
570 | // If there was a match from the UI hint to the msgbox value, use the result from the | ||
571 | // message box. Otherwise, we'll ignore it and return the default to Burn. | ||
572 | if ((e.UIHint & 0xF) == (int)msgbox) | ||
573 | { | ||
574 | e.Result = (Result)result; | ||
575 | } | ||
576 | } | ||
577 | } | ||
578 | } | ||
579 | } | ||
580 | else // canceled, so always return cancel. | ||
581 | { | ||
582 | e.Result = Result.Cancel; | ||
583 | } | ||
584 | } | ||
585 | } | ||
586 | |||
587 | private void ResolveSource(object sender, ResolveSourceEventArgs e) | ||
588 | { | ||
589 | int retries = 0; | ||
590 | |||
591 | this.downloadRetries.TryGetValue(e.PackageOrContainerId, out retries); | ||
592 | this.downloadRetries[e.PackageOrContainerId] = retries + 1; | ||
593 | |||
594 | e.Action = retries < 3 && !String.IsNullOrEmpty(e.DownloadSource) ? BOOTSTRAPPER_RESOLVESOURCE_ACTION.Download : BOOTSTRAPPER_RESOLVESOURCE_ACTION.None; | ||
595 | } | ||
596 | |||
597 | private void ApplyComplete(object sender, ApplyCompleteEventArgs e) | ||
598 | { | ||
599 | WixBA.Model.Result = e.Status; // remember the final result of the apply. | ||
600 | |||
601 | // Set the state to applied or failed unless the state has already been set back to the preapply state | ||
602 | // which means we need to show the UI as it was before the apply started. | ||
603 | if (this.root.InstallState != this.root.PreApplyState) | ||
604 | { | ||
605 | this.root.InstallState = Hresult.Succeeded(e.Status) ? InstallationState.Applied : InstallationState.Failed; | ||
606 | } | ||
607 | |||
608 | // If we're not in Full UI mode, we need to alert the dispatcher to stop and close the window for passive. | ||
609 | if (Bootstrapper.Display.Full != WixBA.Model.Command.Display) | ||
610 | { | ||
611 | // If its passive, send a message to the window to close. | ||
612 | if (Bootstrapper.Display.Passive == WixBA.Model.Command.Display) | ||
613 | { | ||
614 | WixBA.Model.Engine.Log(LogLevel.Verbose, "Automatically closing the window for non-interactive install"); | ||
615 | WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close)); | ||
616 | } | ||
617 | else | ||
618 | { | ||
619 | WixBA.Dispatcher.InvokeShutdown(); | ||
620 | } | ||
621 | return; | ||
622 | } | ||
623 | else if (Hresult.Succeeded(e.Status) && LaunchAction.UpdateReplace == WixBA.Model.PlannedAction) // if we successfully applied an update close the window since the new Bundle should be running now. | ||
624 | { | ||
625 | WixBA.Model.Engine.Log(LogLevel.Verbose, "Automatically closing the window since update successful."); | ||
626 | WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close)); | ||
627 | return; | ||
628 | } | ||
629 | else if (root.AutoClose) | ||
630 | { | ||
631 | // Automatically closing since the user clicked the X button. | ||
632 | WixBA.Dispatcher.BeginInvoke(new Action(WixBA.View.Close)); | ||
633 | return; | ||
634 | } | ||
635 | |||
636 | // Force all commands to reevaluate CanExecute. | ||
637 | // InvalidateRequerySuggested must be run on the UI thread. | ||
638 | root.Dispatcher.Invoke(new Action(CommandManager.InvalidateRequerySuggested)); | ||
639 | } | ||
640 | |||
641 | private void ParseCommandLine() | ||
642 | { | ||
643 | // Get array of arguments based on the system parsing algorithm. | ||
644 | string[] args = WixBA.Model.Command.GetCommandLineArgs(); | ||
645 | for (int i = 0; i < args.Length; ++i) | ||
646 | { | ||
647 | if (args[i].StartsWith("InstallFolder=", StringComparison.InvariantCultureIgnoreCase)) | ||
648 | { | ||
649 | // Allow relative directory paths. Also validates. | ||
650 | string[] param = args[i].Split(new char[] {'='}, 2); | ||
651 | this.root.InstallDirectory = IO.Path.Combine(Environment.CurrentDirectory, param[1]); | ||
652 | } | ||
653 | } | ||
654 | } | ||
655 | |||
656 | private void AddPackageTelemetry(string prefix, string id, double time, int result) | ||
657 | { | ||
658 | lock (this) | ||
659 | { | ||
660 | string key = String.Format("{0}Time_{1}", prefix, id); | ||
661 | string value = time.ToString(); | ||
662 | WixBA.Model.Telemetry.Add(new KeyValuePair<string, string>(key, value)); | ||
663 | |||
664 | key = String.Format("{0}Result_{1}", prefix, id); | ||
665 | value = String.Concat("0x", result.ToString("x")); | ||
666 | WixBA.Model.Telemetry.Add(new KeyValuePair<string, string>(key, value)); | ||
667 | } | ||
668 | } | ||
669 | } | ||
670 | } | ||
diff --git a/src/WixToolset.WixBA/Model.cs b/src/WixToolset.WixBA/Model.cs new file mode 100644 index 00000000..9f03e95b --- /dev/null +++ b/src/WixToolset.WixBA/Model.cs | |||
@@ -0,0 +1,129 @@ | |||
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 | namespace WixToolset.UX | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Net; | ||
8 | using WixToolset.Bootstrapper; | ||
9 | |||
10 | /// <summary> | ||
11 | /// The model. | ||
12 | /// </summary> | ||
13 | public class Model | ||
14 | { | ||
15 | private const string BurnBundleInstallDirectoryVariable = "InstallFolder"; | ||
16 | private const string BurnBundleLayoutDirectoryVariable = "WixBundleLayoutDirectory"; | ||
17 | private const string BurnBundleVersionVariable = "WixBundleVersion"; | ||
18 | |||
19 | /// <summary> | ||
20 | /// Creates a new model for the UX. | ||
21 | /// </summary> | ||
22 | /// <param name="bootstrapper">Bootstrapper hosting the UX.</param> | ||
23 | public Model(BootstrapperApplication bootstrapper) | ||
24 | { | ||
25 | this.Bootstrapper = bootstrapper; | ||
26 | this.Telemetry = new List<KeyValuePair<string, string>>(); | ||
27 | this.Version = this.Engine.VersionVariables[BurnBundleVersionVariable]; | ||
28 | } | ||
29 | |||
30 | /// <summary> | ||
31 | /// Gets the bootstrapper. | ||
32 | /// </summary> | ||
33 | public BootstrapperApplication Bootstrapper { get; private set; } | ||
34 | |||
35 | /// <summary> | ||
36 | /// Gets the bootstrapper command-line. | ||
37 | /// </summary> | ||
38 | public Command Command { get { return this.Bootstrapper.Command; } } | ||
39 | |||
40 | /// <summary> | ||
41 | /// Gets the bootstrapper engine. | ||
42 | /// </summary> | ||
43 | public Engine Engine { get { return this.Bootstrapper.Engine; } } | ||
44 | |||
45 | /// <summary> | ||
46 | /// Gets the key/value pairs used in telemetry. | ||
47 | /// </summary> | ||
48 | public List<KeyValuePair<string, string>> Telemetry { get; private set; } | ||
49 | |||
50 | /// <summary> | ||
51 | /// Get or set the final result of the installation. | ||
52 | /// </summary> | ||
53 | public int Result { get; set; } | ||
54 | |||
55 | /// <summary> | ||
56 | /// Get the version of the install. | ||
57 | /// </summary> | ||
58 | public Version Version { get; private set; } | ||
59 | |||
60 | /// <summary> | ||
61 | /// Get or set the path where the bundle is installed. | ||
62 | /// </summary> | ||
63 | public string InstallDirectory | ||
64 | { | ||
65 | get | ||
66 | { | ||
67 | if (!this.Engine.StringVariables.Contains(BurnBundleInstallDirectoryVariable)) | ||
68 | { | ||
69 | return null; | ||
70 | } | ||
71 | |||
72 | return this.Engine.StringVariables[BurnBundleInstallDirectoryVariable]; | ||
73 | } | ||
74 | |||
75 | set | ||
76 | { | ||
77 | this.Engine.StringVariables[BurnBundleInstallDirectoryVariable] = value; | ||
78 | } | ||
79 | } | ||
80 | |||
81 | /// <summary> | ||
82 | /// Get or set the path for the layout to be created. | ||
83 | /// </summary> | ||
84 | public string LayoutDirectory | ||
85 | { | ||
86 | get | ||
87 | { | ||
88 | if (!this.Engine.StringVariables.Contains(BurnBundleLayoutDirectoryVariable)) | ||
89 | { | ||
90 | return null; | ||
91 | } | ||
92 | |||
93 | return this.Engine.StringVariables[BurnBundleLayoutDirectoryVariable]; | ||
94 | } | ||
95 | |||
96 | set | ||
97 | { | ||
98 | this.Engine.StringVariables[BurnBundleLayoutDirectoryVariable] = value; | ||
99 | } | ||
100 | } | ||
101 | |||
102 | public LaunchAction PlannedAction { get; set; } | ||
103 | |||
104 | /// <summary> | ||
105 | /// Creates a correctly configured HTTP web request. | ||
106 | /// </summary> | ||
107 | /// <param name="uri">URI to connect to.</param> | ||
108 | /// <returns>Correctly configured HTTP web request.</returns> | ||
109 | public HttpWebRequest CreateWebRequest(string uri) | ||
110 | { | ||
111 | HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); | ||
112 | request.UserAgent = String.Concat("WixInstall", this.Version.ToString()); | ||
113 | |||
114 | return request; | ||
115 | } | ||
116 | |||
117 | /// <summary> | ||
118 | /// Gets the display name for a package if possible. | ||
119 | /// </summary> | ||
120 | /// <param name="packageId">Identity of the package to find the display name.</param> | ||
121 | /// <returns>Display name of the package if found or the package id if not.</returns> | ||
122 | public string GetPackageName(string packageId) | ||
123 | { | ||
124 | PackageInfo package; | ||
125 | |||
126 | return this.Bootstrapper.BAManifest.Bundle.Packages.TryGetValue(packageId, out package) ? package.DisplayName : packageId; | ||
127 | } | ||
128 | } | ||
129 | } | ||
diff --git a/src/WixToolset.WixBA/NewsItem.cs b/src/WixToolset.WixBA/NewsItem.cs new file mode 100644 index 00000000..a8350104 --- /dev/null +++ b/src/WixToolset.WixBA/NewsItem.cs | |||
@@ -0,0 +1,18 @@ | |||
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 | namespace WixToolset.UX | ||
4 | { | ||
5 | using System; | ||
6 | |||
7 | /// <summary> | ||
8 | /// The model for an individual news item. | ||
9 | /// </summary> | ||
10 | public class NewsItem | ||
11 | { | ||
12 | public string Author { get; set; } | ||
13 | public string Title { get; set; } | ||
14 | public string Url { get; set; } | ||
15 | public string Snippet { get; set; } | ||
16 | public DateTime Updated { get; set; } | ||
17 | } | ||
18 | } | ||
diff --git a/src/WixToolset.WixBA/ProgressViewModel.cs b/src/WixToolset.WixBA/ProgressViewModel.cs new file mode 100644 index 00000000..30aee5f1 --- /dev/null +++ b/src/WixToolset.WixBA/ProgressViewModel.cs | |||
@@ -0,0 +1,194 @@ | |||
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 | using System; | ||
4 | using System.Collections.Generic; | ||
5 | using System.ComponentModel; | ||
6 | using System.Diagnostics; | ||
7 | using System.Text.RegularExpressions; | ||
8 | using WixToolset.Bootstrapper; | ||
9 | |||
10 | namespace WixToolset.UX | ||
11 | { | ||
12 | public class ProgressViewModel : PropertyNotifyBase | ||
13 | { | ||
14 | private static readonly Regex TrimActionTimeFromMessage = new Regex(@"^\w+\s+\d+:\d+:\d+:\s+", RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.Singleline); | ||
15 | |||
16 | private RootViewModel root; | ||
17 | private Dictionary<string, int> executingPackageOrderIndex; | ||
18 | |||
19 | private int progressPhases; | ||
20 | private int progress; | ||
21 | private int cacheProgress; | ||
22 | private int executeProgress; | ||
23 | private string package; | ||
24 | private string message; | ||
25 | |||
26 | public ProgressViewModel(RootViewModel root) | ||
27 | { | ||
28 | this.root = root; | ||
29 | this.executingPackageOrderIndex = new Dictionary<string, int>(); | ||
30 | |||
31 | this.root.PropertyChanged += this.RootPropertyChanged; | ||
32 | |||
33 | WixBA.Model.Bootstrapper.ExecutePackageBegin += this.ExecutePackageBegin; | ||
34 | WixBA.Model.Bootstrapper.ExecutePackageComplete += this.ExecutePackageComplete; | ||
35 | WixBA.Model.Bootstrapper.ExecuteProgress += this.ApplyExecuteProgress; | ||
36 | WixBA.Model.Bootstrapper.PlanBegin += this.PlanBegin; | ||
37 | WixBA.Model.Bootstrapper.PlanPackageComplete += this.PlanPackageComplete; | ||
38 | WixBA.Model.Bootstrapper.ApplyBegin += this.ApplyBegin; | ||
39 | WixBA.Model.Bootstrapper.Progress += this.ApplyProgress; | ||
40 | WixBA.Model.Bootstrapper.CacheAcquireProgress += this.CacheAcquireProgress; | ||
41 | WixBA.Model.Bootstrapper.CacheComplete += this.CacheComplete; | ||
42 | } | ||
43 | |||
44 | public bool ProgressEnabled | ||
45 | { | ||
46 | get { return this.root.InstallState == InstallationState.Applying; } | ||
47 | } | ||
48 | |||
49 | public int Progress | ||
50 | { | ||
51 | get | ||
52 | { | ||
53 | return this.progress; | ||
54 | } | ||
55 | |||
56 | set | ||
57 | { | ||
58 | if (this.progress != value) | ||
59 | { | ||
60 | this.progress = value; | ||
61 | base.OnPropertyChanged("Progress"); | ||
62 | } | ||
63 | } | ||
64 | } | ||
65 | |||
66 | public string Package | ||
67 | { | ||
68 | get | ||
69 | { | ||
70 | return this.package; | ||
71 | } | ||
72 | |||
73 | set | ||
74 | { | ||
75 | if (this.package != value) | ||
76 | { | ||
77 | this.package = value; | ||
78 | base.OnPropertyChanged("Package"); | ||
79 | } | ||
80 | } | ||
81 | } | ||
82 | |||
83 | public string Message | ||
84 | { | ||
85 | get | ||
86 | { | ||
87 | return this.message; | ||
88 | } | ||
89 | |||
90 | set | ||
91 | { | ||
92 | if (this.message != value) | ||
93 | { | ||
94 | this.message = value; | ||
95 | base.OnPropertyChanged("Message"); | ||
96 | } | ||
97 | } | ||
98 | } | ||
99 | |||
100 | void RootPropertyChanged(object sender, PropertyChangedEventArgs e) | ||
101 | { | ||
102 | if ("InstallState" == e.PropertyName) | ||
103 | { | ||
104 | base.OnPropertyChanged("ProgressEnabled"); | ||
105 | } | ||
106 | } | ||
107 | |||
108 | private void PlanBegin(object sender, PlanBeginEventArgs e) | ||
109 | { | ||
110 | lock (this) | ||
111 | { | ||
112 | this.executingPackageOrderIndex.Clear(); | ||
113 | } | ||
114 | } | ||
115 | |||
116 | private void PlanPackageComplete(object sender, PlanPackageCompleteEventArgs e) | ||
117 | { | ||
118 | if (ActionState.None != e.Execute) | ||
119 | { | ||
120 | lock (this) | ||
121 | { | ||
122 | Debug.Assert(!this.executingPackageOrderIndex.ContainsKey(e.PackageId)); | ||
123 | this.executingPackageOrderIndex.Add(e.PackageId, this.executingPackageOrderIndex.Count); | ||
124 | } | ||
125 | } | ||
126 | } | ||
127 | |||
128 | private void ExecutePackageBegin(object sender, ExecutePackageBeginEventArgs e) | ||
129 | { | ||
130 | lock (this) | ||
131 | { | ||
132 | this.Package = WixBA.Model.GetPackageName(e.PackageId); | ||
133 | this.Message = String.Format("Processing: {0}", this.Package); | ||
134 | e.Cancel = this.root.Canceled; | ||
135 | } | ||
136 | } | ||
137 | |||
138 | private void ExecutePackageComplete(object sender, ExecutePackageCompleteEventArgs e) | ||
139 | { | ||
140 | lock (this) | ||
141 | { // avoid a stale display | ||
142 | this.Message = String.Empty; | ||
143 | } | ||
144 | } | ||
145 | |||
146 | private void ApplyBegin(object sender, ApplyBeginEventArgs e) | ||
147 | { | ||
148 | this.progressPhases = e.PhaseCount; | ||
149 | } | ||
150 | |||
151 | private void ApplyProgress(object sender, ProgressEventArgs e) | ||
152 | { | ||
153 | lock (this) | ||
154 | { | ||
155 | e.Cancel = this.root.Canceled; | ||
156 | } | ||
157 | } | ||
158 | |||
159 | private void CacheAcquireProgress(object sender, CacheAcquireProgressEventArgs e) | ||
160 | { | ||
161 | lock (this) | ||
162 | { | ||
163 | this.cacheProgress = e.OverallPercentage; | ||
164 | this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; | ||
165 | e.Cancel = this.root.Canceled; | ||
166 | } | ||
167 | } | ||
168 | |||
169 | private void CacheComplete(object sender, CacheCompleteEventArgs e) | ||
170 | { | ||
171 | lock (this) | ||
172 | { | ||
173 | this.cacheProgress = 100; | ||
174 | this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; | ||
175 | } | ||
176 | } | ||
177 | |||
178 | private void ApplyExecuteProgress(object sender, ExecuteProgressEventArgs e) | ||
179 | { | ||
180 | lock (this) | ||
181 | { | ||
182 | this.executeProgress = e.OverallPercentage; | ||
183 | this.Progress = (this.cacheProgress + this.executeProgress) / this.progressPhases; | ||
184 | |||
185 | if (WixBA.Model.Command.Display == Display.Embedded) | ||
186 | { | ||
187 | WixBA.Model.Engine.SendEmbeddedProgress(e.ProgressPercentage, this.Progress); | ||
188 | } | ||
189 | |||
190 | e.Cancel = this.root.Canceled; | ||
191 | } | ||
192 | } | ||
193 | } | ||
194 | } | ||
diff --git a/src/WixToolset.WixBA/Properties/AssemblyInfo.cs b/src/WixToolset.WixBA/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..58d88046 --- /dev/null +++ b/src/WixToolset.WixBA/Properties/AssemblyInfo.cs | |||
@@ -0,0 +1,22 @@ | |||
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 | using System; | ||
4 | using System.Reflection; | ||
5 | using System.Runtime.InteropServices; | ||
6 | using WixToolset.Bootstrapper; | ||
7 | using WixToolset.UX; | ||
8 | |||
9 | |||
10 | [assembly: AssemblyTitle("WixBA")] | ||
11 | [assembly: AssemblyDescription("WiX User Experience")] | ||
12 | |||
13 | // Setting ComVisible to false makes the types in this assembly not visible | ||
14 | // to COM components. If you need to access a type in this assembly from | ||
15 | // COM, set the ComVisible attribute to true on that type. | ||
16 | [assembly: ComVisible(false)] | ||
17 | [assembly: Guid("0ffc4944-9295-40b7-adac-3a6864b5219b")] | ||
18 | [assembly: CLSCompliantAttribute(true)] | ||
19 | |||
20 | // Identifies the class that derives from UserExperience and is the UX class that gets | ||
21 | // instantiated by the interop layer | ||
22 | [assembly: BootstrapperApplication(typeof(WixBA))] | ||
diff --git a/src/WixToolset.WixBA/PropertyNotifyBase.cs b/src/WixToolset.WixBA/PropertyNotifyBase.cs new file mode 100644 index 00000000..03174306 --- /dev/null +++ b/src/WixToolset.WixBA/PropertyNotifyBase.cs | |||
@@ -0,0 +1,59 @@ | |||
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 | namespace WixToolset.UX | ||
4 | { | ||
5 | using System; | ||
6 | using System.ComponentModel; | ||
7 | using System.Diagnostics; | ||
8 | |||
9 | /// <summary> | ||
10 | /// It provides support for property change notifications. | ||
11 | /// </summary> | ||
12 | public abstract class PropertyNotifyBase : INotifyPropertyChanged | ||
13 | { | ||
14 | /// <summary> | ||
15 | /// Initializes a new instance of the <see cref="PropertyNotifyBase"/> class. | ||
16 | /// </summary> | ||
17 | protected PropertyNotifyBase() | ||
18 | { | ||
19 | } | ||
20 | |||
21 | /// <summary> | ||
22 | /// Raised when a property on this object has a new value. | ||
23 | /// </summary> | ||
24 | public event PropertyChangedEventHandler PropertyChanged; | ||
25 | |||
26 | /// <summary> | ||
27 | /// Warns the developer if this object does not have a public property with the | ||
28 | /// specified name. This method does not exist in a Release build. | ||
29 | /// </summary> | ||
30 | /// <param name="propertyName">Property name to verify.</param> | ||
31 | [Conditional("DEBUG")] | ||
32 | [DebuggerStepThrough] | ||
33 | public void VerifyPropertyName(string propertyName) | ||
34 | { | ||
35 | // Verify that the property name matches a real, public, instance property | ||
36 | // on this object. | ||
37 | if (null == TypeDescriptor.GetProperties(this)[propertyName]) | ||
38 | { | ||
39 | Debug.Fail(String.Concat("Invalid property name: ", propertyName)); | ||
40 | } | ||
41 | } | ||
42 | |||
43 | /// <summary> | ||
44 | /// Raises this object's PropertyChanged event. | ||
45 | /// </summary> | ||
46 | /// <param name="propertyName">The property that has a new value.</param> | ||
47 | protected virtual void OnPropertyChanged(string propertyName) | ||
48 | { | ||
49 | this.VerifyPropertyName(propertyName); | ||
50 | |||
51 | PropertyChangedEventHandler handler = this.PropertyChanged; | ||
52 | if (null != handler) | ||
53 | { | ||
54 | PropertyChangedEventArgs e = new PropertyChangedEventArgs(propertyName); | ||
55 | handler(this, e); | ||
56 | } | ||
57 | } | ||
58 | } | ||
59 | } | ||
diff --git a/src/WixToolset.WixBA/RelayCommand.cs b/src/WixToolset.WixBA/RelayCommand.cs new file mode 100644 index 00000000..ecc482da --- /dev/null +++ b/src/WixToolset.WixBA/RelayCommand.cs | |||
@@ -0,0 +1,45 @@ | |||
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 | namespace WixToolset.UX | ||
4 | { | ||
5 | using System; | ||
6 | using System.Diagnostics; | ||
7 | using System.Windows.Input; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Base class that implements ICommand interface via delegates. | ||
11 | /// </summary> | ||
12 | public class RelayCommand : ICommand | ||
13 | { | ||
14 | private readonly Action<object> execute; | ||
15 | private readonly Predicate<object> canExecute; | ||
16 | |||
17 | public RelayCommand(Action<object> execute) | ||
18 | : this(execute, null) | ||
19 | { | ||
20 | } | ||
21 | |||
22 | public RelayCommand(Action<object> execute, Predicate<object> canExecute) | ||
23 | { | ||
24 | this.execute = execute; | ||
25 | this.canExecute = canExecute; | ||
26 | } | ||
27 | |||
28 | public event EventHandler CanExecuteChanged | ||
29 | { | ||
30 | add { CommandManager.RequerySuggested += value; } | ||
31 | remove { CommandManager.RequerySuggested -= value; } | ||
32 | } | ||
33 | |||
34 | [DebuggerStepThrough] | ||
35 | public bool CanExecute(object parameter) | ||
36 | { | ||
37 | return this.canExecute == null ? true : this.canExecute(parameter); | ||
38 | } | ||
39 | |||
40 | public void Execute(object parameter) | ||
41 | { | ||
42 | this.execute(parameter); | ||
43 | } | ||
44 | } | ||
45 | } | ||
diff --git a/src/WixToolset.WixBA/Resources/logo-black-hollow.png b/src/WixToolset.WixBA/Resources/logo-black-hollow.png new file mode 100644 index 00000000..9d0290bd --- /dev/null +++ b/src/WixToolset.WixBA/Resources/logo-black-hollow.png | |||
Binary files differ | |||
diff --git a/src/WixToolset.WixBA/Resources/logo-white-hollow.png b/src/WixToolset.WixBA/Resources/logo-white-hollow.png new file mode 100644 index 00000000..242c7350 --- /dev/null +++ b/src/WixToolset.WixBA/Resources/logo-white-hollow.png | |||
Binary files differ | |||
diff --git a/src/WixToolset.WixBA/RootView.xaml b/src/WixToolset.WixBA/RootView.xaml new file mode 100644 index 00000000..bbf2f9f2 --- /dev/null +++ b/src/WixToolset.WixBA/RootView.xaml | |||
@@ -0,0 +1,420 @@ | |||
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 | <Window x:Class="WixToolset.UX.RootView" | ||
5 | xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
6 | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
7 | xmlns:ux="clr-namespace:WixToolset.UX" | ||
8 | MinWidth="550" | ||
9 | MinHeight="400" | ||
10 | Width="750" | ||
11 | Height="400" | ||
12 | AllowsTransparency="False" | ||
13 | Background="{x:Null}" | ||
14 | Closing="Window_Closing" | ||
15 | ResizeMode="CanResizeWithGrip" | ||
16 | WindowStartupLocation="CenterScreen" | ||
17 | WindowStyle="ThreeDBorderWindow"> | ||
18 | |||
19 | <Window.Resources> | ||
20 | <ResourceDictionary> | ||
21 | <ResourceDictionary.MergedDictionaries> | ||
22 | <ResourceDictionary Source="pack://application:,,,/WixBA;component/Styles.xaml" /> | ||
23 | </ResourceDictionary.MergedDictionaries> | ||
24 | </ResourceDictionary> | ||
25 | </Window.Resources> | ||
26 | |||
27 | <Border Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" | ||
28 | BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}" | ||
29 | BorderThickness="0"> | ||
30 | |||
31 | <DockPanel x:Name="AppArtBoardArea" | ||
32 | Margin="20, 10"> | ||
33 | |||
34 | <DockPanel x:Name="ActionArea" | ||
35 | DockPanel.Dock="Bottom"> | ||
36 | |||
37 | <Grid x:Name="ActionGrid"> | ||
38 | <Grid.RowDefinitions> | ||
39 | <RowDefinition Height="Auto" /> | ||
40 | </Grid.RowDefinitions> | ||
41 | <Grid.ColumnDefinitions> | ||
42 | <ColumnDefinition Width="*" MinWidth="150" /> | ||
43 | <ColumnDefinition Width="Auto" /> | ||
44 | </Grid.ColumnDefinitions> | ||
45 | |||
46 | <StackPanel x:Name="LeftMarqueeDcIsland" | ||
47 | Grid.Column="0" | ||
48 | VerticalAlignment="Center" | ||
49 | DataContext="{Binding UpdateViewModel}"> | ||
50 | <ProgressBar x:Name="CheckingUpdatesProgress" | ||
51 | MinHeight="10" | ||
52 | IsIndeterminate="{Binding CheckingEnabled}" | ||
53 | Style="{DynamicResource UpdateMarqueeStyle}" | ||
54 | Visibility="{Binding CheckingEnabled, | ||
55 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
56 | </StackPanel> | ||
57 | |||
58 | <StackPanel x:Name="InitiateActionArea" | ||
59 | Grid.Column="1" | ||
60 | Margin="30, 0, 0, 0" | ||
61 | HorizontalAlignment="Right" | ||
62 | Orientation="Horizontal"> | ||
63 | <!-- Displayed from left to right, when ordered from top to bottom. Most preferred is at the top. --> | ||
64 | <StackPanel x:Name="UpdateBtnDcIsland" | ||
65 | DataContext="{Binding UpdateViewModel}" | ||
66 | Style="{DynamicResource ActionBtnStkPnlStyle}"> | ||
67 | <Button AutomationProperties.HelpText="An update is available. Click to get the update" | ||
68 | AutomationProperties.Name="Update" | ||
69 | Command="{Binding UpdateCommand}" | ||
70 | Content="Update" | ||
71 | Style="{DynamicResource ActionButtonStyle}" | ||
72 | Visibility="{Binding CanUpdate, | ||
73 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
74 | </StackPanel> | ||
75 | <StackPanel x:Name="InstallBtnDcIsland" | ||
76 | DataContext="{Binding InstallationViewModel}" | ||
77 | Style="{DynamicResource ActionBtnStkPnlStyle}"> | ||
78 | <Button AutomationProperties.HelpText="Click to accept license and install" | ||
79 | AutomationProperties.Name="Install" | ||
80 | Command="{Binding InstallCommand}" | ||
81 | Content="_Install" | ||
82 | IsDefault="True" | ||
83 | Style="{DynamicResource ActionButtonStyle}" | ||
84 | Visibility="{Binding InstallEnabled, | ||
85 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
86 | </StackPanel> | ||
87 | <StackPanel x:Name="RepairBtnDcIsland" | ||
88 | DataContext="{Binding InstallationViewModel}" | ||
89 | Style="{DynamicResource ActionBtnStkPnlStyle}"> | ||
90 | <Button x:Name="RepairButton" | ||
91 | AutomationProperties.HelpText="Click to Repair" | ||
92 | AutomationProperties.Name="Repair" | ||
93 | Command="{Binding RepairCommand}" | ||
94 | Content="Repair" | ||
95 | Style="{DynamicResource ActionButtonStyle}" | ||
96 | Visibility="{Binding RepairEnabled, | ||
97 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
98 | </StackPanel> | ||
99 | <StackPanel x:Name="UninstallBtnDcIsland" | ||
100 | DataContext="{Binding InstallationViewModel}" | ||
101 | Style="{DynamicResource ActionBtnStkPnlStyle}"> | ||
102 | <Button AutomationProperties.HelpText="Click to Uninstall" | ||
103 | AutomationProperties.Name="Uninstall" | ||
104 | Command="{Binding UninstallCommand}" | ||
105 | Content="Uninstall" | ||
106 | Style="{DynamicResource ActionButtonStyle}" | ||
107 | Visibility="{Binding UninstallEnabled, | ||
108 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
109 | </StackPanel> | ||
110 | <StackPanel x:Name="TryAgainBtnDcIsland" | ||
111 | DataContext="{Binding InstallationViewModel}" | ||
112 | Style="{DynamicResource ActionBtnStkPnlStyle}"> | ||
113 | <Button HorizontalAlignment="Center" | ||
114 | VerticalAlignment="Center" | ||
115 | AutomationProperties.HelpText="Click to try again" | ||
116 | AutomationProperties.Name="Try Again?" | ||
117 | Command="{Binding TryAgainCommand}" | ||
118 | Content="Try Again?" | ||
119 | Style="{DynamicResource ActionButtonStyle}" | ||
120 | Visibility="{Binding IsFailedCompletion, | ||
121 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
122 | </StackPanel> | ||
123 | |||
124 | <!-- Final Actions - only one of these is expected to be displayed, if any --> | ||
125 | <StackPanel x:Name="CloseBtnDcIsland" | ||
126 | HorizontalAlignment="Right" | ||
127 | DataContext="{Binding InstallationViewModel}" | ||
128 | Style="{DynamicResource ActionBtnStkPnlStyle}"> | ||
129 | <Button x:Name="CloseButton" | ||
130 | AutomationProperties.HelpText="Click to Close" | ||
131 | AutomationProperties.Name="Close" | ||
132 | Command="{Binding CloseCommand}" | ||
133 | Content="Close" | ||
134 | Style="{DynamicResource FinalActionButtonStyle}" | ||
135 | Visibility="{Binding IsSuccessfulCompletion, | ||
136 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
137 | </StackPanel> | ||
138 | <StackPanel x:Name="CancelBtnDcIsland" | ||
139 | HorizontalAlignment="Right" | ||
140 | DataContext="{Binding}" | ||
141 | Style="{DynamicResource ActionBtnStkPnlStyle}"> | ||
142 | <Button AutomationProperties.HelpText="Press to Cancel" | ||
143 | AutomationProperties.Name="Cancel" | ||
144 | Command="{Binding CancelCommand}" | ||
145 | Content="Cancel" | ||
146 | Style="{DynamicResource FinalActionButtonStyle}" | ||
147 | Visibility="{Binding CancelAvailable, | ||
148 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
149 | </StackPanel> | ||
150 | </StackPanel> | ||
151 | </Grid> | ||
152 | </DockPanel> | ||
153 | |||
154 | <StackPanel x:Name="FinalStatusArea" | ||
155 | DataContext="{Binding InstallationViewModel}" | ||
156 | HorizontalAlignment="Right" | ||
157 | Margin="0,10,0,20" | ||
158 | Orientation="Horizontal" | ||
159 | Visibility="{Binding IsComplete, | ||
160 | Converter={StaticResource BooleanToVisibilityConverter}}" | ||
161 | DockPanel.Dock="Bottom"> | ||
162 | <TextBlock x:Name="StatusTextBlk" | ||
163 | Style="{DynamicResource LabelTextBlkStyle}" | ||
164 | Text="{Binding StatusText, StringFormat={}{0}:}" | ||
165 | TextBlock.FontWeight="Bold" /> | ||
166 | <Button x:Name="ViewLogLink" | ||
167 | Margin="10,2,10,0" | ||
168 | Command="{Binding OpenLogCommand}" | ||
169 | Style="{StaticResource HyperlinkedButtonStyle}"> | ||
170 | <TextBlock> | ||
171 | <Hyperlink Command="{Binding OpenLogCommand}"><Run FontSize="{DynamicResource FontSizeButton}" Text="View Log" /></Hyperlink> | ||
172 | </TextBlock> | ||
173 | </Button> | ||
174 | <Button x:Name="ViewLogFolderLink" | ||
175 | Command="{Binding OpenLogFolderCommand}" | ||
176 | Style="{StaticResource HyperlinkedButtonStyle}"> | ||
177 | <TextBlock> | ||
178 | <Hyperlink Command="{Binding OpenLogFolderCommand}"><Run FontSize="{DynamicResource FontSizeButton}" Text="View Log Folder" /></Hyperlink> | ||
179 | </TextBlock> | ||
180 | </Button> | ||
181 | </StackPanel> | ||
182 | |||
183 | <StackPanel x:Name="StatusStkPnlDcIsland" | ||
184 | Margin="0,10" | ||
185 | DataContext="{Binding ProgressViewModel}" | ||
186 | DockPanel.Dock="Bottom"> | ||
187 | |||
188 | <StackPanel x:Name="ActionPackageNameTextStkPnl" | ||
189 | VerticalAlignment="Top"> | ||
190 | <TextBlock Style="{StaticResource StatusTextStyle}" | ||
191 | Text="{Binding Message}" | ||
192 | TextWrapping="WrapWithOverflow" | ||
193 | Visibility="{Binding ProgressEnabled, | ||
194 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
195 | </StackPanel> | ||
196 | |||
197 | <StackPanel x:Name="ActionProgressStkPnl" | ||
198 | Margin="0,5,0,0"> | ||
199 | <ProgressBar x:Name="ActionProgress" | ||
200 | Height="20" | ||
201 | VerticalAlignment="Center" | ||
202 | Style="{DynamicResource ActionProgressStyle}" | ||
203 | Visibility="{Binding ProgressEnabled, | ||
204 | Converter={StaticResource BooleanToVisibilityConverter}}" | ||
205 | Value="{Binding Progress}" /> | ||
206 | </StackPanel> | ||
207 | </StackPanel> | ||
208 | |||
209 | <StackPanel x:Name="TitleAndLogoStkPnl" | ||
210 | DockPanel.Dock="Top"> | ||
211 | <Grid x:Name="TitleGrid"> | ||
212 | <Grid.RowDefinitions> | ||
213 | <RowDefinition Height="Auto" /> | ||
214 | </Grid.RowDefinitions> | ||
215 | <Grid.ColumnDefinitions> | ||
216 | <ColumnDefinition Width="Auto" /> | ||
217 | <ColumnDefinition Width="*" /> | ||
218 | <ColumnDefinition Width="Auto" /> | ||
219 | </Grid.ColumnDefinitions> | ||
220 | |||
221 | <TextBlock x:Name="TitleTextBlk" | ||
222 | Grid.Column="0" | ||
223 | Style="{DynamicResource TitleTextBlkStyle}" | ||
224 | Text="{Binding Title, | ||
225 | Mode=OneTime}" /> | ||
226 | <Image x:Name="Logo" | ||
227 | Grid.Column="2" | ||
228 | Style="{DynamicResource LogoStyle}"> | ||
229 | </Image> | ||
230 | </Grid> | ||
231 | </StackPanel> | ||
232 | |||
233 | <Grid x:Name="MainStkPnl"> | ||
234 | <Grid.RowDefinitions> | ||
235 | <RowDefinition Height="*" /> | ||
236 | <RowDefinition Height="Auto" /> | ||
237 | </Grid.RowDefinitions> | ||
238 | <Grid.ColumnDefinitions> | ||
239 | <ColumnDefinition Width="Auto" /> | ||
240 | <ColumnDefinition Width="1*" /> | ||
241 | <ColumnDefinition Width="4*" MinWidth="200" /> | ||
242 | </Grid.ColumnDefinitions> | ||
243 | |||
244 | <Grid x:Name="SkuInfo" | ||
245 | Grid.Column="0" | ||
246 | Grid.Row="0" | ||
247 | DataContext="{Binding InstallationViewModel}"> | ||
248 | <Grid.RowDefinitions> | ||
249 | <RowDefinition Height="Auto" /> | ||
250 | <RowDefinition Height="Auto" /> | ||
251 | <RowDefinition Height="Auto" /> | ||
252 | <RowDefinition Height="Auto" /> | ||
253 | <RowDefinition Height="Auto" /> | ||
254 | <RowDefinition Height="Auto" /> | ||
255 | <RowDefinition Height="Auto" /> | ||
256 | </Grid.RowDefinitions> | ||
257 | <Grid.ColumnDefinitions> | ||
258 | <ColumnDefinition Width="Auto" /> | ||
259 | <ColumnDefinition Width="*" /> | ||
260 | </Grid.ColumnDefinitions> | ||
261 | |||
262 | <TextBlock x:Name="SkuPublisherLabel" | ||
263 | Grid.Row="0" | ||
264 | Grid.Column="0" | ||
265 | Style="{DynamicResource LabelTextBlkStyle}" | ||
266 | Text="Publisher:" /> | ||
267 | <TextBlock x:Name="SkuPublisherData" | ||
268 | Grid.Row="0" | ||
269 | Grid.Column="1" | ||
270 | Style="{DynamicResource DataTextBlkStyle}" | ||
271 | Text="{Binding Publisher, | ||
272 | Mode=OneTime}" /> | ||
273 | |||
274 | <TextBlock x:Name="SkuVersionLabel" | ||
275 | Grid.Row="1" | ||
276 | Grid.Column="0" | ||
277 | Style="{DynamicResource LabelTextBlkStyle}" | ||
278 | Text="Version:" /> | ||
279 | <TextBlock Grid.Row="1" | ||
280 | Grid.Column="1" | ||
281 | Style="{DynamicResource DataTextBlkStyle}" | ||
282 | Text="{Binding Version}" /> | ||
283 | |||
284 | <TextBlock x:Name="SkuLicenseLabel" | ||
285 | Grid.Row="2" | ||
286 | Grid.Column="0" | ||
287 | Style="{DynamicResource LabelTextBlkStyle}" | ||
288 | Text="License:" /> | ||
289 | <Button x:Name="SkuLicenseBtn" | ||
290 | Grid.Row="2" | ||
291 | Grid.Column="1" | ||
292 | HorizontalAlignment="Left" | ||
293 | AutomationProperties.HelpText="View the license" | ||
294 | AutomationProperties.Name="License" | ||
295 | Command="{Binding LicenseCommand}" | ||
296 | KeyboardNavigation.IsTabStop="False" | ||
297 | Style="{StaticResource HyperlinkedButtonStyle}"> | ||
298 | <TextBlock HorizontalAlignment="Left"> | ||
299 | <Hyperlink Command="{Binding LicenseCommand}" | ||
300 | IsEnabled="True" | ||
301 | KeyboardNavigation.IsTabStop="False"><Run FontSize="{DynamicResource FontSizeButton}" Text="View License" /></Hyperlink> | ||
302 | </TextBlock> | ||
303 | </Button> | ||
304 | |||
305 | <TextBlock x:Name="SkuNewsLabel" | ||
306 | Grid.Row="3" | ||
307 | Grid.Column="0" | ||
308 | Style="{DynamicResource LabelTextBlkStyle}" | ||
309 | Text="News:" /> | ||
310 | <Button x:Name="SkuNewsBtn" | ||
311 | Grid.Row="3" | ||
312 | Grid.Column="1" | ||
313 | HorizontalAlignment="Left" | ||
314 | AutomationProperties.HelpText="Latest News" | ||
315 | AutomationProperties.Name="News" | ||
316 | Command="{Binding LaunchNewsCommand}" | ||
317 | KeyboardNavigation.IsTabStop="False" | ||
318 | Style="{StaticResource HyperlinkedButtonStyle}"> | ||
319 | <TextBlock HorizontalAlignment="Left"> | ||
320 | <Hyperlink Command="{Binding LaunchNewsCommand}" | ||
321 | IsEnabled="True" | ||
322 | KeyboardNavigation.IsTabStop="False"><Run FontSize="{DynamicResource FontSizeButton}" Text="Latest News" /></Hyperlink> | ||
323 | </TextBlock> | ||
324 | </Button> | ||
325 | |||
326 | <TextBlock x:Name="SkuSupportLabel" | ||
327 | Grid.Row="4" | ||
328 | Grid.Column="0" | ||
329 | Style="{DynamicResource LabelTextBlkStyle}" | ||
330 | Text="Support:" /> | ||
331 | <Button x:Name="SkuSupportBtn" | ||
332 | Grid.Row="4" | ||
333 | Grid.Column="1" | ||
334 | HorizontalAlignment="Left" | ||
335 | AutomationProperties.HelpText="View Home Page for Support" | ||
336 | AutomationProperties.Name="Home Page" | ||
337 | Command="{Binding LaunchHomePageCommand}" | ||
338 | KeyboardNavigation.IsTabStop="False" | ||
339 | Style="{StaticResource HyperlinkedButtonStyle}"> | ||
340 | <TextBlock HorizontalAlignment="Left"> | ||
341 | <Hyperlink Command="{Binding LaunchHomePageCommand}" | ||
342 | IsEnabled="True" | ||
343 | KeyboardNavigation.IsTabStop="False"><Run FontSize="{DynamicResource FontSizeButton}" Text="{Binding SupportUrl, Mode=OneTime}" /></Hyperlink> | ||
344 | </TextBlock> | ||
345 | </Button> | ||
346 | |||
347 | <TextBlock x:Name="SkuVSExtensionLabel1" | ||
348 | Grid.Row="5" | ||
349 | Grid.Column="0" | ||
350 | Style="{DynamicResource LabelTextBlkStyle}" | ||
351 | Text="Visual Studio" /> | ||
352 | <TextBlock x:Name="SkuVSExtensionLabel2" | ||
353 | Grid.Row="6" | ||
354 | Grid.Column="0" | ||
355 | Style="{DynamicResource LabelTextBlkStyle}" | ||
356 | Text="Extension:" /> | ||
357 | <Button x:Name="SkuVSExtensionBtn" | ||
358 | Grid.Row="6" | ||
359 | Grid.Column="1" | ||
360 | HorizontalAlignment="Left" | ||
361 | AutomationProperties.HelpText="View Releases Page for VS Extension" | ||
362 | AutomationProperties.Name="Releases Page" | ||
363 | Command="{Binding LaunchVSExtensionPageCommand}" | ||
364 | KeyboardNavigation.IsTabStop="False" | ||
365 | Style="{StaticResource HyperlinkedButtonStyle}"> | ||
366 | <TextBlock HorizontalAlignment="Left"> | ||
367 | <Hyperlink Command="{Binding LaunchVSExtensionPageCommand}" | ||
368 | IsEnabled="True" | ||
369 | KeyboardNavigation.IsTabStop="False"><Run FontSize="{DynamicResource FontSizeButton}" Text="{Binding VSExtensionUrl, Mode=OneTime}" /></Hyperlink> | ||
370 | </TextBlock> | ||
371 | </Button> | ||
372 | </Grid> | ||
373 | |||
374 | <DockPanel x:Name="UpdateChangesStkPnlDcIsland" | ||
375 | Grid.Row="0" | ||
376 | Grid.Column="2" | ||
377 | DataContext="{Binding UpdateViewModel}" | ||
378 | Visibility="{Binding IsUpdateAvailable, | ||
379 | Converter={StaticResource BooleanToVisibilityConverter}}"> | ||
380 | |||
381 | <Grid x:Name="UpdateInfoGrid" | ||
382 | DockPanel.Dock="Top"> | ||
383 | <Grid.RowDefinitions> | ||
384 | <RowDefinition Height="Auto" /> | ||
385 | </Grid.RowDefinitions> | ||
386 | <Grid.ColumnDefinitions> | ||
387 | <ColumnDefinition Width="Auto" /> | ||
388 | <ColumnDefinition Width="*" /> | ||
389 | </Grid.ColumnDefinitions> | ||
390 | |||
391 | <TextBlock x:Name="UpdateTitleLabel" | ||
392 | Grid.Row="0" | ||
393 | Grid.Column="0" | ||
394 | Style="{DynamicResource LabelTextBlkStyle}" | ||
395 | Text="Available Update:" /> | ||
396 | |||
397 | <TextBlock x:Name="UpdateVersionLabel" | ||
398 | Grid.Row="0" | ||
399 | Grid.Column="1" | ||
400 | Style="{DynamicResource DataTextBlkStyle}" | ||
401 | Text="{Binding UpdateVersion}" /> | ||
402 | </Grid> | ||
403 | |||
404 | <WebBrowser DockPanel.Dock="Bottom" | ||
405 | ux:BrowserProperties.HtmlDoc="{Binding UpdateChanges}" /> | ||
406 | </DockPanel> | ||
407 | |||
408 | <TextBlock x:Name="DowngradeMessageTextBlk" | ||
409 | Grid.Row="1" | ||
410 | Grid.Column="0" | ||
411 | Grid.ColumnSpan="3" | ||
412 | DataContext="{Binding InstallationViewModel}" | ||
413 | Style="{DynamicResource LabelTextBlkStyle}" | ||
414 | Text="{Binding DowngradeMessage}" | ||
415 | Visibility="{Binding Downgrade, | ||
416 | Converter={StaticResource BooleanToVisibilityConverter}}" /> | ||
417 | </Grid> | ||
418 | </DockPanel> | ||
419 | </Border> | ||
420 | </Window> | ||
diff --git a/src/WixToolset.WixBA/RootView.xaml.cs b/src/WixToolset.WixBA/RootView.xaml.cs new file mode 100644 index 00000000..1d4301f2 --- /dev/null +++ b/src/WixToolset.WixBA/RootView.xaml.cs | |||
@@ -0,0 +1,51 @@ | |||
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 | namespace WixToolset.UX | ||
4 | { | ||
5 | using System.ComponentModel; | ||
6 | using System.Windows; | ||
7 | using System.Windows.Interop; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Interaction logic for View.xaml | ||
11 | /// </summary> | ||
12 | public partial class RootView : Window | ||
13 | { | ||
14 | /// <summary> | ||
15 | /// Creates the view populated with it's model. | ||
16 | /// </summary> | ||
17 | /// <param name="viewModel">Model for the view.</param> | ||
18 | public RootView(RootViewModel viewModel) | ||
19 | { | ||
20 | this.DataContext = viewModel; | ||
21 | |||
22 | this.Loaded += (sender, e) => WixBA.Model.Engine.CloseSplashScreen(); | ||
23 | this.Closed += (sender, e) => this.Dispatcher.InvokeShutdown(); // shutdown dispatcher when the window is closed. | ||
24 | |||
25 | this.InitializeComponent(); | ||
26 | |||
27 | viewModel.Dispatcher = this.Dispatcher; | ||
28 | viewModel.ViewWindowHandle = new WindowInteropHelper(this).EnsureHandle(); | ||
29 | } | ||
30 | |||
31 | /// <summary> | ||
32 | /// Event is fired when the window is closing. | ||
33 | /// </summary> | ||
34 | /// <param name="sender"></param> | ||
35 | /// <param name="e"></param> | ||
36 | private void Window_Closing(object sender, CancelEventArgs e) | ||
37 | { | ||
38 | RootViewModel rvm = this.DataContext as RootViewModel; | ||
39 | if ((null != rvm) && (InstallationState.Applying == rvm.InstallState)) | ||
40 | { | ||
41 | rvm.CancelButton_Click(); | ||
42 | if (rvm.Canceled) | ||
43 | { | ||
44 | // Defer closing until the engine has canceled processing. | ||
45 | e.Cancel = true; | ||
46 | rvm.AutoClose = true; | ||
47 | } | ||
48 | } | ||
49 | } | ||
50 | } | ||
51 | } | ||
diff --git a/src/WixToolset.WixBA/RootViewModel.cs b/src/WixToolset.WixBA/RootViewModel.cs new file mode 100644 index 00000000..1de89adf --- /dev/null +++ b/src/WixToolset.WixBA/RootViewModel.cs | |||
@@ -0,0 +1,204 @@ | |||
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 | namespace WixToolset.UX | ||
4 | { | ||
5 | using System; | ||
6 | using System.Diagnostics; | ||
7 | using System.Reflection; | ||
8 | using System.Windows; | ||
9 | using System.Windows.Input; | ||
10 | using System.Windows.Threading; | ||
11 | using WixToolset.Bootstrapper; | ||
12 | |||
13 | /// <summary> | ||
14 | /// The errors returned from the engine | ||
15 | /// </summary> | ||
16 | public enum Error | ||
17 | { | ||
18 | UserCancelled = 1223, | ||
19 | } | ||
20 | |||
21 | /// <summary> | ||
22 | /// The model of the root view in WixBA. | ||
23 | /// </summary> | ||
24 | public class RootViewModel : PropertyNotifyBase | ||
25 | { | ||
26 | private ICommand cancelCommand; | ||
27 | private ICommand closeCommand; | ||
28 | |||
29 | private bool canceled; | ||
30 | private InstallationState installState; | ||
31 | private DetectionState detectState; | ||
32 | |||
33 | /// <summary> | ||
34 | /// Creates a new model of the root view. | ||
35 | /// </summary> | ||
36 | public RootViewModel() | ||
37 | { | ||
38 | this.InstallationViewModel = new InstallationViewModel(this); | ||
39 | this.ProgressViewModel = new ProgressViewModel(this); | ||
40 | this.UpdateViewModel = new UpdateViewModel(this); | ||
41 | } | ||
42 | |||
43 | public InstallationViewModel InstallationViewModel { get; private set; } | ||
44 | public ProgressViewModel ProgressViewModel { get; private set; } | ||
45 | public UpdateViewModel UpdateViewModel { get; private set; } | ||
46 | public Dispatcher Dispatcher { get; set; } | ||
47 | public IntPtr ViewWindowHandle { get; set; } | ||
48 | public bool AutoClose { get; set; } | ||
49 | |||
50 | public ICommand CloseCommand | ||
51 | { | ||
52 | get | ||
53 | { | ||
54 | if (this.closeCommand == null) | ||
55 | { | ||
56 | this.closeCommand = new RelayCommand(param => WixBA.View.Close()); | ||
57 | } | ||
58 | |||
59 | return this.closeCommand; | ||
60 | } | ||
61 | } | ||
62 | |||
63 | public ICommand CancelCommand | ||
64 | { | ||
65 | get | ||
66 | { | ||
67 | if (this.cancelCommand == null) | ||
68 | { | ||
69 | this.cancelCommand = new RelayCommand(param => | ||
70 | { | ||
71 | this.CancelButton_Click(); | ||
72 | }, | ||
73 | param => !this.Canceled); | ||
74 | } | ||
75 | |||
76 | return this.cancelCommand; | ||
77 | } | ||
78 | } | ||
79 | |||
80 | public bool CancelAvailable | ||
81 | { | ||
82 | get { return InstallationState.Applying == this.InstallState; } | ||
83 | } | ||
84 | |||
85 | public bool Canceled | ||
86 | { | ||
87 | get | ||
88 | { | ||
89 | return this.canceled; | ||
90 | } | ||
91 | |||
92 | set | ||
93 | { | ||
94 | if (this.canceled != value) | ||
95 | { | ||
96 | this.canceled = value; | ||
97 | base.OnPropertyChanged("Canceled"); | ||
98 | } | ||
99 | } | ||
100 | } | ||
101 | |||
102 | /// <summary> | ||
103 | /// Gets and sets the detect state of the view's model. | ||
104 | /// </summary> | ||
105 | public DetectionState DetectState | ||
106 | { | ||
107 | get | ||
108 | { | ||
109 | return this.detectState; | ||
110 | } | ||
111 | |||
112 | set | ||
113 | { | ||
114 | if (this.detectState != value) | ||
115 | { | ||
116 | this.detectState = value; | ||
117 | |||
118 | // Notify all the properties derived from the state that the state changed. | ||
119 | base.OnPropertyChanged("DetectState"); | ||
120 | } | ||
121 | } | ||
122 | } | ||
123 | |||
124 | /// <summary> | ||
125 | /// Gets and sets the installation state of the view's model. | ||
126 | /// </summary> | ||
127 | public InstallationState InstallState | ||
128 | { | ||
129 | get | ||
130 | { | ||
131 | return this.installState; | ||
132 | } | ||
133 | |||
134 | set | ||
135 | { | ||
136 | if (this.installState != value) | ||
137 | { | ||
138 | this.installState = value; | ||
139 | |||
140 | // Notify all the properties derived from the state that the state changed. | ||
141 | base.OnPropertyChanged("InstallState"); | ||
142 | base.OnPropertyChanged("CancelAvailable"); | ||
143 | } | ||
144 | } | ||
145 | } | ||
146 | |||
147 | /// <summary> | ||
148 | /// Gets and sets the state of the view's model before apply begins in order to return to that state if cancel or rollback occurs. | ||
149 | /// </summary> | ||
150 | public InstallationState PreApplyState { get; set; } | ||
151 | |||
152 | /// <summary> | ||
153 | /// Gets and sets the path where the bundle is currently installed or will be installed. | ||
154 | /// </summary> | ||
155 | public string InstallDirectory | ||
156 | { | ||
157 | get | ||
158 | { | ||
159 | return WixBA.Model.InstallDirectory; | ||
160 | } | ||
161 | |||
162 | set | ||
163 | { | ||
164 | if (WixBA.Model.InstallDirectory != value) | ||
165 | { | ||
166 | WixBA.Model.InstallDirectory = value; | ||
167 | base.OnPropertyChanged("InstallDirectory"); | ||
168 | } | ||
169 | } | ||
170 | } | ||
171 | |||
172 | /// <summary> | ||
173 | /// The Title of this bundle. | ||
174 | /// </summary> | ||
175 | public string Title | ||
176 | { | ||
177 | get | ||
178 | { | ||
179 | return WixDistribution.ShortProduct; | ||
180 | } | ||
181 | } | ||
182 | |||
183 | /// <summary> | ||
184 | /// Prompts the user to make sure they want to cancel. | ||
185 | /// This needs to run on the UI thread, use Dispatcher.Invoke to call this from a background thread. | ||
186 | /// </summary> | ||
187 | public void CancelButton_Click() | ||
188 | { | ||
189 | if (this.Canceled) | ||
190 | { | ||
191 | return; | ||
192 | } | ||
193 | |||
194 | if (Display.Full == WixBA.Model.Command.Display) | ||
195 | { | ||
196 | this.Canceled = (MessageBoxResult.Yes == MessageBox.Show(WixBA.View, "Are you sure you want to cancel?", "WiX Toolset", MessageBoxButton.YesNo, MessageBoxImage.Error)); | ||
197 | } | ||
198 | else | ||
199 | { | ||
200 | this.Canceled = true; | ||
201 | } | ||
202 | } | ||
203 | } | ||
204 | } | ||
diff --git a/src/WixToolset.WixBA/Styles.xaml b/src/WixToolset.WixBA/Styles.xaml new file mode 100644 index 00000000..89ccd2ea --- /dev/null +++ b/src/WixToolset.WixBA/Styles.xaml | |||
@@ -0,0 +1,194 @@ | |||
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 | <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
5 | xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
6 | xmlns:System="clr-namespace:System;assembly=mscorlib" | ||
7 | xmlns:po="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" | ||
8 | xmlns:shell="clr-namespace:System.Windows.Shell;assembly=PresentationFramework" | ||
9 | xmlns:ux="clr-namespace:WixToolset.UX"> | ||
10 | |||
11 | <!-- Converters --> | ||
12 | <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" /> | ||
13 | |||
14 | <!-- Fonts --> | ||
15 | <FontFamily x:Key="FontFamily">Segoe UI, Arial</FontFamily> | ||
16 | <System:Double x:Key="FontSizeBranding">45</System:Double> | ||
17 | <System:Double x:Key="FontSizeMedium">12</System:Double> | ||
18 | <System:Double x:Key="FontSizeButton">14</System:Double> | ||
19 | |||
20 | <!-- Images --> | ||
21 | <BitmapImage x:Key="LogoOverLightBackground" UriSource="pack://application:,,,/WixBA;component/resources/logo-white-hollow.png" /> | ||
22 | <BitmapImage x:Key="LogoOverDarkBackground" UriSource="pack://application:,,,/WixBA;component/resources/logo-black-hollow.png" /> | ||
23 | |||
24 | <!-- Colors --> | ||
25 | <Color x:Key="ProgressIndicatorColor">#FF1EF1E8</Color> | ||
26 | |||
27 | <!-- Brushs --> | ||
28 | <SolidColorBrush x:Key="ProgressIndicatorBrush" Color="{DynamicResource ProgressIndicatorColor}" /> | ||
29 | |||
30 | <LinearGradientBrush x:Key="ProgressBarIndicatorAnimatedFill" StartPoint="0,0" EndPoint="1,0"> | ||
31 | <LinearGradientBrush.GradientStops> | ||
32 | <GradientStopCollection> | ||
33 | <GradientStop Offset="0" Color="#000000FF" /> | ||
34 | <GradientStop Offset="0.5" Color="#600000FF" /> | ||
35 | <GradientStop Offset="0.54" Color="{DynamicResource {x:Static SystemColors.ControlTextColorKey}}" /> | ||
36 | <GradientStop Offset="0.56" Color="{DynamicResource {x:Static SystemColors.ControlTextColorKey}}" /> | ||
37 | <GradientStop Offset="0.6" Color="#600000FF" /> | ||
38 | <GradientStop Offset="1" Color="#000000FF" /> | ||
39 | </GradientStopCollection> | ||
40 | </LinearGradientBrush.GradientStops> | ||
41 | </LinearGradientBrush> | ||
42 | |||
43 | <!-- Control Templates --> | ||
44 | <ControlTemplate x:Key="HyperlinkedButtonTemplateKey" TargetType="{x:Type Button}"> | ||
45 | <ContentPresenter Margin="{TemplateBinding Control.Padding}" | ||
46 | HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" | ||
47 | VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" | ||
48 | Content="{TemplateBinding ContentControl.Content}" | ||
49 | ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" | ||
50 | SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" /> | ||
51 | </ControlTemplate> | ||
52 | |||
53 | <ControlTemplate x:Key="ProgressBarTemplateKey" TargetType="{x:Type ProgressBar}"> | ||
54 | <Border Name="TemplateRoot" | ||
55 | Margin="0,5" | ||
56 | BorderBrush="{TemplateBinding BorderBrush}" | ||
57 | BorderThickness="{TemplateBinding BorderThickness}" | ||
58 | CornerRadius="3"> | ||
59 | <Grid ClipToBounds="True" SnapsToDevicePixels="true"> | ||
60 | <Rectangle Fill="{TemplateBinding Background}" /> | ||
61 | <Rectangle Name="PART_Track" ClipToBounds="True" /> | ||
62 | <Decorator x:Name="PART_Indicator" HorizontalAlignment="Left"> | ||
63 | <Grid Name="Foreground"> | ||
64 | <Rectangle x:Name="Indicator" Fill="{TemplateBinding Foreground}" /> | ||
65 | <Grid x:Name="Animation"> | ||
66 | <Rectangle x:Name="PART_GlowRect" | ||
67 | Width="80" | ||
68 | Margin="-100,0,0,0" | ||
69 | HorizontalAlignment="Left" | ||
70 | Fill="{StaticResource ProgressBarIndicatorAnimatedFill}" /> | ||
71 | </Grid> | ||
72 | </Grid> | ||
73 | </Decorator> | ||
74 | </Grid> | ||
75 | </Border> | ||
76 | </ControlTemplate> | ||
77 | |||
78 | <!-- Styles --> | ||
79 | <Style x:Key="ActionBtnStkPnlStyle" TargetType="StackPanel"> | ||
80 | <Setter Property="Margin" Value="0,2,0,0" /> | ||
81 | <Setter Property="HorizontalAlignment" Value="Center" /> | ||
82 | </Style> | ||
83 | |||
84 | <Style x:Key="FinalActionsStkPnlStyle" TargetType="StackPanel"> | ||
85 | <Setter Property="Margin" Value="80,2,0,0" /> | ||
86 | </Style> | ||
87 | |||
88 | <Style x:Key="BrandStkPnlStyle" TargetType="StackPanel"> | ||
89 | <Setter Property="Margin" Value="0,0,20,0" /> | ||
90 | <Setter Property="VerticalAlignment" Value="Top" /> | ||
91 | <Setter Property="HorizontalAlignment" Value="Right" /> | ||
92 | <Setter Property="Width" Value="100" /> | ||
93 | <Setter Property="Width" Value="100" /> | ||
94 | </Style> | ||
95 | |||
96 | <Style x:Key="CommonTextBlkStyle" TargetType="TextBlock"> | ||
97 | <Setter Property="VerticalAlignment" Value="Center" /> | ||
98 | <Setter Property="HorizontalAlignment" Value="Center" /> | ||
99 | <Setter Property="FontWeight" Value="Bold" /> | ||
100 | <Setter Property="TextAlignment" Value="Center" /> | ||
101 | <Setter Property="TextWrapping" Value="WrapWithOverflow" /> | ||
102 | <Setter Property="FontFamily" Value="{DynamicResource FontFamily}" /> | ||
103 | </Style> | ||
104 | |||
105 | <Style x:Key="TitleTextBlkStyle" | ||
106 | BasedOn="{StaticResource CommonTextBlkStyle}" | ||
107 | TargetType="TextBlock"> | ||
108 | <Setter Property="VerticalAlignment" Value="Top" /> | ||
109 | <Setter Property="HorizontalAlignment" Value="Left" /> | ||
110 | <Setter Property="FontSize" Value="{DynamicResource ResourceKey=FontSizeBranding}" /> | ||
111 | <Setter Property="FontWeight" Value="ExtraBold" /> | ||
112 | <Setter Property="Margin" Value="0,5,0,0" /> | ||
113 | </Style> | ||
114 | |||
115 | |||
116 | <Style x:Key="LabelTextBlkStyle" | ||
117 | BasedOn="{StaticResource CommonTextBlkStyle}" | ||
118 | TargetType="TextBlock"> | ||
119 | <Setter Property="HorizontalAlignment" Value="Left" /> | ||
120 | <Setter Property="FontSize" Value="{DynamicResource FontSizeButton}" /> | ||
121 | <Setter Property="FontWeight" Value="Bold" /> | ||
122 | <Setter Property="TextAlignment" Value="Left" /> | ||
123 | <Setter Property="Margin" Value="0,2,3,0" /> | ||
124 | </Style> | ||
125 | |||
126 | <Style x:Key="DataTextBlkStyle" | ||
127 | BasedOn="{StaticResource CommonTextBlkStyle}" | ||
128 | TargetType="TextBlock"> | ||
129 | <Setter Property="HorizontalAlignment" Value="Left" /> | ||
130 | <Setter Property="FontSize" Value="{DynamicResource FontSizeMedium}" /> | ||
131 | <Setter Property="TextAlignment" Value="Left" /> | ||
132 | <Setter Property="Margin" Value="2,2,3,0" /> | ||
133 | </Style> | ||
134 | |||
135 | <Style x:Key="StatusTextStyle" | ||
136 | BasedOn="{StaticResource CommonTextBlkStyle}" | ||
137 | TargetType="TextBlock"> | ||
138 | <Setter Property="FontFamily" Value="{DynamicResource {x:Static SystemFonts.MessageFontFamilyKey}}" /> | ||
139 | <Setter Property="FontSize" Value="{DynamicResource FontSizeMedium}" /> | ||
140 | <Setter Property="Margin" Value="8" /> | ||
141 | <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" /> | ||
142 | <Setter Property="VerticalAlignment" Value="Top" /> | ||
143 | <Setter Property="HorizontalAlignment" Value="Left" /> | ||
144 | </Style> | ||
145 | |||
146 | <Style x:Key="ActionButtonStyle" TargetType="Button"> | ||
147 | <Setter Property="MinHeight" Value="30" /> | ||
148 | <Setter Property="MinWidth" Value="100" /> | ||
149 | <Setter Property="Margin" Value="5,0,0,0" /> | ||
150 | <Setter Property="VerticalAlignment" Value="Center" /> | ||
151 | <Setter Property="VerticalContentAlignment" Value="Center" /> | ||
152 | <Setter Property="HorizontalAlignment" Value="Center" /> | ||
153 | <Setter Property="HorizontalContentAlignment" Value="Center" /> | ||
154 | <Setter Property="FontFamily" Value="{DynamicResource FontFamily}" /> | ||
155 | </Style> | ||
156 | |||
157 | <Style x:Key="FinalActionButtonStyle" | ||
158 | BasedOn="{StaticResource ActionButtonStyle}" | ||
159 | TargetType="Button"> | ||
160 | <Setter Property="Margin" Value="40,0,0,0" /> | ||
161 | </Style> | ||
162 | |||
163 | <Style x:Key="HyperlinkedButtonStyle" TargetType="Button"> | ||
164 | <Setter Property="Margin" Value="0,2,0,0" /> | ||
165 | <Setter Property="Template" Value="{StaticResource HyperlinkedButtonTemplateKey}" /> | ||
166 | <Setter Property="IsHitTestVisible" Value="True" /> | ||
167 | </Style> | ||
168 | |||
169 | <Style x:Key="LogoStyle" TargetType="Image"> | ||
170 | <Setter Property="Height" Value="65" /> | ||
171 | <Setter Property="Width" Value="102" /> | ||
172 | <Setter Property="VerticalAlignment" Value="Top" /> | ||
173 | <Setter Property="IsHitTestVisible" Value="False" /> | ||
174 | <Setter Property="Source" Value="{DynamicResource LogoOverLightBackground}" /> | ||
175 | <Style.Triggers> | ||
176 | <DataTrigger Binding="{Binding Path=IsLightBackground, Source={x:Static ux:WindowProperties.Instance}}" Value="false"> | ||
177 | <Setter Property="Source" Value="{DynamicResource LogoOverDarkBackground}" /> | ||
178 | </DataTrigger> | ||
179 | </Style.Triggers> | ||
180 | </Style> | ||
181 | |||
182 | <Style x:Key="UpdateMarqueeStyle" TargetType="ProgressBar"> | ||
183 | <Setter Property="Foreground" Value="{DynamicResource ProgressIndicatorBrush}" /> | ||
184 | <Setter Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" /> | ||
185 | <Setter Property="BorderThickness" Value="1" /> | ||
186 | </Style> | ||
187 | |||
188 | <Style x:Key="ActionProgressStyle" TargetType="ProgressBar"> | ||
189 | <Setter Property="Foreground" Value="{DynamicResource ProgressIndicatorBrush}" /> | ||
190 | <Setter Property="BorderBrush" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" /> | ||
191 | <Setter Property="BorderThickness" Value="1" /> | ||
192 | <Setter Property="Template" Value="{StaticResource ProgressBarTemplateKey}" /> | ||
193 | </Style> | ||
194 | </ResourceDictionary> | ||
diff --git a/src/WixToolset.WixBA/UpdateViewModel.cs b/src/WixToolset.WixBA/UpdateViewModel.cs new file mode 100644 index 00000000..6b60112c --- /dev/null +++ b/src/WixToolset.WixBA/UpdateViewModel.cs | |||
@@ -0,0 +1,207 @@ | |||
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 | namespace WixToolset.UX | ||
4 | { | ||
5 | using System; | ||
6 | using System.ComponentModel; | ||
7 | using System.Windows.Input; | ||
8 | using WixToolset.Bootstrapper; | ||
9 | |||
10 | /// <summary> | ||
11 | /// The states of the update view model. | ||
12 | /// </summary> | ||
13 | public enum UpdateState | ||
14 | { | ||
15 | Unknown, | ||
16 | Initializing, | ||
17 | Checking, | ||
18 | Current, | ||
19 | Available, | ||
20 | Failed, | ||
21 | } | ||
22 | |||
23 | /// <summary> | ||
24 | /// The model of the update view. | ||
25 | /// </summary> | ||
26 | public class UpdateViewModel : PropertyNotifyBase | ||
27 | { | ||
28 | private RootViewModel root; | ||
29 | private UpdateState state; | ||
30 | private ICommand updateCommand; | ||
31 | private string updateVersion; | ||
32 | private string updateChanges; | ||
33 | |||
34 | |||
35 | public UpdateViewModel(RootViewModel root) | ||
36 | { | ||
37 | this.root = root; | ||
38 | WixBA.Model.Bootstrapper.DetectUpdateBegin += this.DetectUpdateBegin; | ||
39 | WixBA.Model.Bootstrapper.DetectUpdate += this.DetectUpdate; | ||
40 | WixBA.Model.Bootstrapper.DetectUpdateComplete += this.DetectUpdateComplete; | ||
41 | |||
42 | this.root.PropertyChanged += new PropertyChangedEventHandler(this.RootPropertyChanged); | ||
43 | |||
44 | this.State = UpdateState.Initializing; | ||
45 | } | ||
46 | |||
47 | void RootPropertyChanged(object sender, PropertyChangedEventArgs e) | ||
48 | { | ||
49 | if ("InstallState" == e.PropertyName) | ||
50 | { | ||
51 | base.OnPropertyChanged("CanUpdate"); | ||
52 | } | ||
53 | } | ||
54 | |||
55 | public bool CheckingEnabled | ||
56 | { | ||
57 | get { return this.State == UpdateState.Initializing || this.State == UpdateState.Checking; } | ||
58 | } | ||
59 | |||
60 | public bool CanUpdate | ||
61 | { | ||
62 | get | ||
63 | { | ||
64 | switch(this.root.InstallState) | ||
65 | { | ||
66 | case InstallationState.Waiting: | ||
67 | case InstallationState.Applied: | ||
68 | case InstallationState.Failed: | ||
69 | return this.IsUpdateAvailable; | ||
70 | default: | ||
71 | return false; | ||
72 | } | ||
73 | } | ||
74 | } | ||
75 | |||
76 | public ICommand UpdateCommand | ||
77 | { | ||
78 | get | ||
79 | { | ||
80 | if (this.updateCommand == null) | ||
81 | { | ||
82 | this.updateCommand = new RelayCommand(param => WixBA.Plan(LaunchAction.UpdateReplace), param => this.CanUpdate); | ||
83 | } | ||
84 | |||
85 | return this.updateCommand; | ||
86 | } | ||
87 | } | ||
88 | |||
89 | public bool IsUpdateAvailable | ||
90 | { | ||
91 | get { return this.State == UpdateState.Available; } | ||
92 | } | ||
93 | |||
94 | /// <summary> | ||
95 | /// Gets and sets the state of the update view model. | ||
96 | /// </summary> | ||
97 | public UpdateState State | ||
98 | { | ||
99 | get | ||
100 | { | ||
101 | return this.state; | ||
102 | } | ||
103 | |||
104 | set | ||
105 | { | ||
106 | if (this.state != value) | ||
107 | { | ||
108 | this.state = value; | ||
109 | base.OnPropertyChanged("State"); | ||
110 | base.OnPropertyChanged("CanUpdate"); | ||
111 | base.OnPropertyChanged("CheckingEnabled"); | ||
112 | base.OnPropertyChanged("IsUpdateAvailable"); | ||
113 | } | ||
114 | } | ||
115 | } | ||
116 | /// <summary> | ||
117 | /// The version of an available update. | ||
118 | /// </summary> | ||
119 | public string UpdateVersion | ||
120 | { | ||
121 | get | ||
122 | { | ||
123 | return updateVersion; | ||
124 | } | ||
125 | set | ||
126 | { | ||
127 | if (this.updateVersion != value) | ||
128 | { | ||
129 | this.updateVersion = value; | ||
130 | base.OnPropertyChanged("UpdateVersion"); | ||
131 | } | ||
132 | } | ||
133 | } | ||
134 | |||
135 | /// <summary> | ||
136 | /// The changes in the available update. | ||
137 | /// </summary> | ||
138 | public string UpdateChanges | ||
139 | { | ||
140 | get | ||
141 | { | ||
142 | return updateChanges; | ||
143 | } | ||
144 | set | ||
145 | { | ||
146 | if (this.updateChanges != value) | ||
147 | { | ||
148 | this.updateChanges = value; | ||
149 | base.OnPropertyChanged("UpdateChanges"); | ||
150 | } | ||
151 | } | ||
152 | } | ||
153 | |||
154 | private void DetectUpdateBegin(object sender, Bootstrapper.DetectUpdateBeginEventArgs e) | ||
155 | { | ||
156 | // Don't check for updates if: | ||
157 | // the first check failed (no retry) | ||
158 | // if we are being run as an uninstall | ||
159 | // if we are not under a full UI. | ||
160 | if ((UpdateState.Failed != this.State) && (LaunchAction.Uninstall != WixBA.Model.Command.Action) && (Display.Full == WixBA.Model.Command.Display)) | ||
161 | { | ||
162 | this.State = UpdateState.Checking; | ||
163 | e.Skip = false; | ||
164 | } | ||
165 | } | ||
166 | |||
167 | private void DetectUpdate(object sender, Bootstrapper.DetectUpdateEventArgs e) | ||
168 | { | ||
169 | // The list of updates is sorted in descending version, so the first callback should be the largest update available. | ||
170 | // This update should be either larger than ours (so we are out of date), the same as ours (so we are current) | ||
171 | // or smaller than ours (we have a private build). If we really wanted to, we could leave the e.StopProcessingUpdates alone and | ||
172 | // enumerate all of the updates. | ||
173 | WixBA.Model.Engine.Log(LogLevel.Verbose, String.Format("Potential update v{0} from '{1}'; current version: v{2}", e.Version, e.UpdateLocation, WixBA.Model.Version)); | ||
174 | if (e.Version > WixBA.Model.Version) | ||
175 | { | ||
176 | WixBA.Model.Engine.SetUpdate(null, e.UpdateLocation, e.Size, UpdateHashType.None, null); | ||
177 | this.UpdateVersion = String.Concat("v", e.Version.ToString()); | ||
178 | string changesFormat = @"<body style='overflow: auto;'>{0}</body>"; | ||
179 | this.UpdateChanges = String.Format(changesFormat, e.Content); | ||
180 | this.State = UpdateState.Available; | ||
181 | } | ||
182 | else | ||
183 | { | ||
184 | this.State = UpdateState.Current; | ||
185 | } | ||
186 | e.StopProcessingUpdates = true; | ||
187 | } | ||
188 | |||
189 | private void DetectUpdateComplete(object sender, Bootstrapper.DetectUpdateCompleteEventArgs e) | ||
190 | { | ||
191 | // Failed to process an update, allow the existing bundle to still install. | ||
192 | if ((UpdateState.Failed != this.State) && !Hresult.Succeeded(e.Status)) | ||
193 | { | ||
194 | this.State = UpdateState.Failed; | ||
195 | WixBA.Model.Engine.Log(LogLevel.Verbose, String.Format("Failed to locate an update, status of 0x{0:X8}, updates disabled.", e.Status)); | ||
196 | e.IgnoreError = true; | ||
197 | } | ||
198 | // If we are uninstalling, we don't want to check or show an update | ||
199 | // If we are checking, then the feed didn't find any valid enclosures | ||
200 | // If we are initializing, we're either uninstalling or not a full UI | ||
201 | else if ((LaunchAction.Uninstall == WixBA.Model.Command.Action) || (UpdateState.Initializing == this.State) || (UpdateState.Checking == this.State)) | ||
202 | { | ||
203 | this.State = UpdateState.Unknown; | ||
204 | } | ||
205 | } | ||
206 | } | ||
207 | } | ||
diff --git a/src/WixToolset.WixBA/WindowProperties.cs b/src/WixToolset.WixBA/WindowProperties.cs new file mode 100644 index 00000000..bead5cc1 --- /dev/null +++ b/src/WixToolset.WixBA/WindowProperties.cs | |||
@@ -0,0 +1,65 @@ | |||
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 | namespace WixToolset.UX | ||
4 | { | ||
5 | using System; | ||
6 | using System.Windows; | ||
7 | using System.Windows.Media; | ||
8 | |||
9 | /// <summary> | ||
10 | /// Dependency Properties associated with the main Window object. | ||
11 | /// </summary> | ||
12 | public class WindowProperties : DependencyObject | ||
13 | { | ||
14 | /// <summary> | ||
15 | /// Dependency Property to hold the result of detecting the relative luminosity (or brightness) of a Windows background. | ||
16 | /// </summary> | ||
17 | public static readonly DependencyProperty IsLightBackgroundProperty = DependencyProperty.Register( | ||
18 | "IsLightBackground", typeof(bool), typeof(WindowProperties), new PropertyMetadata( false )); | ||
19 | |||
20 | private static Lazy<WindowProperties> _instance = new Lazy<WindowProperties>(() => | ||
21 | { | ||
22 | WindowProperties wp = new WindowProperties(); | ||
23 | wp.CheckBackgroundBrightness(); | ||
24 | return wp; | ||
25 | }); | ||
26 | |||
27 | public static WindowProperties Instance | ||
28 | { | ||
29 | get | ||
30 | { | ||
31 | return _instance.Value; | ||
32 | } | ||
33 | } | ||
34 | |||
35 | |||
36 | public bool IsLightBackground | ||
37 | { | ||
38 | get { return (bool)GetValue(IsLightBackgroundProperty); } | ||
39 | private set { SetValue(IsLightBackgroundProperty, value); } | ||
40 | } | ||
41 | |||
42 | /// <summary> | ||
43 | /// Use the Luminosity parameter of the background color to detect light vs dark theme settings. | ||
44 | /// </summary> | ||
45 | /// <remarks> | ||
46 | /// This approach detects both the common High Contrast themes (White vs Black) and custom themes which may have relatively lighter backgrounds. | ||
47 | /// </remarks> | ||
48 | public void CheckBackgroundBrightness() | ||
49 | { | ||
50 | SolidColorBrush windowbrush = System.Windows.SystemColors.WindowBrush; | ||
51 | System.Drawing.Color dcolor = System.Drawing.Color.FromArgb(windowbrush.Color.A, windowbrush.Color.R, windowbrush.Color.G, windowbrush.Color.B); | ||
52 | |||
53 | var brightness = dcolor.GetBrightness(); | ||
54 | // Test for 'Lightness' at an arbitrary point, approaching 1.0 (White). | ||
55 | if (0.7 < brightness) | ||
56 | { | ||
57 | this.IsLightBackground = true; | ||
58 | } | ||
59 | else | ||
60 | { | ||
61 | this.IsLightBackground = false; | ||
62 | } | ||
63 | } | ||
64 | } | ||
65 | } | ||
diff --git a/src/WixToolset.WixBA/WixBA.BootstrapperCore.config b/src/WixToolset.WixBA/WixBA.BootstrapperCore.config new file mode 100644 index 00000000..8e1d4729 --- /dev/null +++ b/src/WixToolset.WixBA/WixBA.BootstrapperCore.config | |||
@@ -0,0 +1,16 @@ | |||
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 | <configuration> | ||
5 | <configSections> | ||
6 | <sectionGroup name="wix.bootstrapper" type="WixToolset.Bootstrapper.BootstrapperSectionGroup, BootstrapperCore"> | ||
7 | <section name="host" type="WixToolset.Bootstrapper.HostSection, BootstrapperCore" /> | ||
8 | </sectionGroup> | ||
9 | </configSections> | ||
10 | <startup useLegacyV2RuntimeActivationPolicy="true"> | ||
11 | <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> | ||
12 | </startup> | ||
13 | <wix.bootstrapper> | ||
14 | <host assemblyName="WixBA" /> | ||
15 | </wix.bootstrapper> | ||
16 | </configuration> | ||
diff --git a/src/WixToolset.WixBA/WixBA.cs b/src/WixToolset.WixBA/WixBA.cs new file mode 100644 index 00000000..fb69a346 --- /dev/null +++ b/src/WixToolset.WixBA/WixBA.cs | |||
@@ -0,0 +1,216 @@ | |||
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 | namespace WixToolset.UX | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | using System.Diagnostics; | ||
8 | using System.IO; | ||
9 | using System.Net; | ||
10 | using System.Text; | ||
11 | using System.Windows.Input; | ||
12 | using Threading = System.Windows.Threading; | ||
13 | using WinForms = System.Windows.Forms; | ||
14 | |||
15 | using WixToolset.Bootstrapper; | ||
16 | |||
17 | /// <summary> | ||
18 | /// The WiX toolset user experience. | ||
19 | /// </summary> | ||
20 | public class WixBA : BootstrapperApplication | ||
21 | { | ||
22 | /// <summary> | ||
23 | /// Gets the global model. | ||
24 | /// </summary> | ||
25 | static public Model Model { get; private set; } | ||
26 | |||
27 | /// <summary> | ||
28 | /// Gets the global view. | ||
29 | /// </summary> | ||
30 | static public RootView View { get; private set; } | ||
31 | // TODO: We should refactor things so we dont have a global View. | ||
32 | |||
33 | /// <summary> | ||
34 | /// Gets the global dispatcher. | ||
35 | /// </summary> | ||
36 | static public Threading.Dispatcher Dispatcher { get; private set; } | ||
37 | |||
38 | /// <summary> | ||
39 | /// Launches the default web browser to the provided URI. | ||
40 | /// </summary> | ||
41 | /// <param name="uri">URI to open the web browser.</param> | ||
42 | public static void LaunchUrl(string uri) | ||
43 | { | ||
44 | WixBA.UseShellExecute(uri); | ||
45 | } | ||
46 | |||
47 | /// <summary> | ||
48 | /// Open a log file. | ||
49 | /// </summary> | ||
50 | /// <param name="uri">URI to a log file.</param> | ||
51 | internal static void OpenLog(Uri uri) | ||
52 | { | ||
53 | WixBA.UseShellExecute(uri.ToString()); | ||
54 | } | ||
55 | |||
56 | /// <summary> | ||
57 | /// Open a log folder. | ||
58 | /// </summary> | ||
59 | /// <param name="string">path to a log folder.</param> | ||
60 | internal static void OpenLogFolder(string logFolder) | ||
61 | { | ||
62 | WixBA.UseShellExecute(logFolder); | ||
63 | } | ||
64 | |||
65 | /// <summary> | ||
66 | /// Open a log folder. | ||
67 | /// </summary> | ||
68 | /// <param name="uri">path to a log folder.</param> | ||
69 | private static void UseShellExecute(string path) | ||
70 | { | ||
71 | // Switch the wait cursor since shellexec can take a second or so. | ||
72 | System.Windows.Input.Cursor cursor = WixBA.View.Cursor; | ||
73 | WixBA.View.Cursor = System.Windows.Input.Cursors.Wait; | ||
74 | Process process = null; | ||
75 | try | ||
76 | { | ||
77 | process = new Process(); | ||
78 | process.StartInfo.FileName = path; | ||
79 | process.StartInfo.UseShellExecute = true; | ||
80 | process.StartInfo.Verb = "open"; | ||
81 | |||
82 | process.Start(); | ||
83 | } | ||
84 | finally | ||
85 | { | ||
86 | if (null != process) | ||
87 | { | ||
88 | process.Dispose(); | ||
89 | } | ||
90 | // back to the original cursor. | ||
91 | WixBA.View.Cursor = cursor; | ||
92 | } | ||
93 | } | ||
94 | |||
95 | /// <summary> | ||
96 | /// Starts planning the appropriate action. | ||
97 | /// </summary> | ||
98 | /// <param name="action">Action to plan.</param> | ||
99 | public static void Plan(LaunchAction action) | ||
100 | { | ||
101 | WixBA.Model.PlannedAction = action; | ||
102 | WixBA.Model.Engine.Plan(WixBA.Model.PlannedAction); | ||
103 | } | ||
104 | |||
105 | public static void PlanLayout() | ||
106 | { | ||
107 | // Either default or set the layout directory | ||
108 | if (String.IsNullOrEmpty(WixBA.Model.Command.LayoutDirectory)) | ||
109 | { | ||
110 | WixBA.Model.LayoutDirectory = Directory.GetCurrentDirectory(); | ||
111 | |||
112 | // Ask the user for layout folder if one wasn't provided and we're in full UI mode | ||
113 | if (WixBA.Model.Command.Display == Display.Full) | ||
114 | { | ||
115 | WixBA.Dispatcher.Invoke((Action)delegate() | ||
116 | { | ||
117 | WinForms.FolderBrowserDialog browserDialog = new WinForms.FolderBrowserDialog(); | ||
118 | browserDialog.RootFolder = Environment.SpecialFolder.MyComputer; | ||
119 | |||
120 | // Default to the current directory. | ||
121 | browserDialog.SelectedPath = WixBA.Model.LayoutDirectory; | ||
122 | WinForms.DialogResult result = browserDialog.ShowDialog(); | ||
123 | |||
124 | if (WinForms.DialogResult.OK == result) | ||
125 | { | ||
126 | WixBA.Model.LayoutDirectory = browserDialog.SelectedPath; | ||
127 | WixBA.Plan(WixBA.Model.Command.Action); | ||
128 | } | ||
129 | else | ||
130 | { | ||
131 | WixBA.View.Close(); | ||
132 | } | ||
133 | } | ||
134 | ); | ||
135 | } | ||
136 | } | ||
137 | else | ||
138 | { | ||
139 | WixBA.Model.LayoutDirectory = WixBA.Model.Command.LayoutDirectory; | ||
140 | WixBA.Plan(WixBA.Model.Command.Action); | ||
141 | } | ||
142 | } | ||
143 | |||
144 | /// <summary> | ||
145 | /// Thread entry point for WiX Toolset UX. | ||
146 | /// </summary> | ||
147 | protected override void Run() | ||
148 | { | ||
149 | this.Engine.Log(LogLevel.Verbose, "Running the WiX BA."); | ||
150 | WixBA.Model = new Model(this); | ||
151 | WixBA.Dispatcher = Threading.Dispatcher.CurrentDispatcher; | ||
152 | RootViewModel viewModel = new RootViewModel(); | ||
153 | |||
154 | // Kick off detect which will populate the view models. | ||
155 | this.Engine.Detect(); | ||
156 | |||
157 | // Create a Window to show UI. | ||
158 | if (WixBA.Model.Command.Display == Display.Passive || | ||
159 | WixBA.Model.Command.Display == Display.Full) | ||
160 | { | ||
161 | this.Engine.Log(LogLevel.Verbose, "Creating a UI."); | ||
162 | WixBA.View = new RootView(viewModel); | ||
163 | WixBA.View.Show(); | ||
164 | } | ||
165 | |||
166 | Threading.Dispatcher.Run(); | ||
167 | |||
168 | this.PostTelemetry(); | ||
169 | this.Engine.Quit(WixBA.Model.Result); | ||
170 | } | ||
171 | |||
172 | private void PostTelemetry() | ||
173 | { | ||
174 | string result = String.Concat("0x", WixBA.Model.Result.ToString("x")); | ||
175 | |||
176 | StringBuilder telemetryData = new StringBuilder(); | ||
177 | foreach (KeyValuePair<string, string> kvp in WixBA.Model.Telemetry) | ||
178 | { | ||
179 | telemetryData.AppendFormat("{0}={1}+", kvp.Key, kvp.Value); | ||
180 | } | ||
181 | telemetryData.AppendFormat("Result={0}", result); | ||
182 | |||
183 | byte[] data = Encoding.UTF8.GetBytes(telemetryData.ToString()); | ||
184 | |||
185 | try | ||
186 | { | ||
187 | HttpWebRequest post = WixBA.Model.CreateWebRequest(String.Format(WixDistribution.TelemetryUrlFormat, WixBA.Model.Version.ToString(), result)); | ||
188 | post.Method = "POST"; | ||
189 | post.ContentType = "application/x-www-form-urlencoded"; | ||
190 | post.ContentLength = data.Length; | ||
191 | |||
192 | using (Stream postStream = post.GetRequestStream()) | ||
193 | { | ||
194 | postStream.Write(data, 0, data.Length); | ||
195 | } | ||
196 | |||
197 | HttpWebResponse response = (HttpWebResponse)post.GetResponse(); | ||
198 | } | ||
199 | catch (ArgumentException) | ||
200 | { | ||
201 | } | ||
202 | catch (FormatException) | ||
203 | { | ||
204 | } | ||
205 | catch (OverflowException) | ||
206 | { | ||
207 | } | ||
208 | catch (ProtocolViolationException) | ||
209 | { | ||
210 | } | ||
211 | catch (WebException) | ||
212 | { | ||
213 | } | ||
214 | } | ||
215 | } | ||
216 | } | ||
diff --git a/src/WixToolset.WixBA/WixBA.csproj b/src/WixToolset.WixBA/WixBA.csproj new file mode 100644 index 00000000..6858b172 --- /dev/null +++ b/src/WixToolset.WixBA/WixBA.csproj | |||
@@ -0,0 +1,64 @@ | |||
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 | <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0"> | ||
4 | <PropertyGroup> | ||
5 | <ProjectGuid>{7C27518B-84AD-4679-8EF4-29DF552CF1AC}</ProjectGuid> | ||
6 | <AssemblyName>WixBA</AssemblyName> | ||
7 | <OutputType>Library</OutputType> | ||
8 | <RootNamespace>WixToolset.UX</RootNamespace> | ||
9 | </PropertyGroup> | ||
10 | <ItemGroup> | ||
11 | <Compile Include="Hresult.cs" /> | ||
12 | <Compile Include="Model.cs" /> | ||
13 | <Compile Include="BrowserProperties.cs" /> | ||
14 | <Compile Include="NewsItem.cs" /> | ||
15 | <Compile Include="ProgressViewModel.cs" /> | ||
16 | <Compile Include="RelayCommand.cs" /> | ||
17 | <Compile Include="InstallationViewModel.cs" /> | ||
18 | <Page Include="Styles.xaml"> | ||
19 | <Generator>MSBuild:Compile</Generator> | ||
20 | <SubType>Designer</SubType> | ||
21 | </Page> | ||
22 | <Page Include="RootView.xaml"> | ||
23 | <Generator>MSBuild:Compile</Generator> | ||
24 | <SubType>Designer</SubType> | ||
25 | </Page> | ||
26 | <Compile Include="PropertyNotifyBase.cs" /> | ||
27 | <Compile Include="RootView.xaml.cs"> | ||
28 | <DependentUpon>RootView.xaml</DependentUpon> | ||
29 | </Compile> | ||
30 | <Compile Include="RootViewModel.cs" /> | ||
31 | <Compile Include="UpdateViewModel.cs" /> | ||
32 | <Compile Include="WindowProperties.cs" /> | ||
33 | <Compile Include="WixBA.cs" /> | ||
34 | <Compile Include="Properties\AssemblyInfo.cs" /> | ||
35 | <None Include="WixBA.BootstrapperCore.config"> | ||
36 | <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | ||
37 | <SubType>Designer</SubType> | ||
38 | </None> | ||
39 | </ItemGroup> | ||
40 | <ItemGroup> | ||
41 | <Reference Include="PresentationCore" /> | ||
42 | <Reference Include="PresentationFramework" /> | ||
43 | <Reference Include="System" /> | ||
44 | <Reference Include="System.ComponentModel.DataAnnotations" /> | ||
45 | <Reference Include="System.Core" /> | ||
46 | <Reference Include="System.Drawing"> | ||
47 | <Private>False</Private> | ||
48 | </Reference> | ||
49 | <Reference Include="System.ServiceModel" /> | ||
50 | <Reference Include="System.Windows.Forms" /> | ||
51 | <Reference Include="System.Xml.Linq" /> | ||
52 | <Reference Include="System.Xml" /> | ||
53 | <Reference Include="System.Xaml" /> | ||
54 | <Reference Include="WindowsBase" /> | ||
55 | <ProjectReference Include="..\..\ext\BalExtension\mba\core\core.csproj" /> | ||
56 | </ItemGroup> | ||
57 | <ItemGroup> | ||
58 | <Resource Include="Resources\logo-white-hollow.png" /> | ||
59 | </ItemGroup> | ||
60 | <ItemGroup> | ||
61 | <Resource Include="Resources\logo-black-hollow.png" /> | ||
62 | </ItemGroup> | ||
63 | <Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildProjectDirectory), wix.proj))\tools\WixBuild.targets" /> | ||
64 | </Project> \ No newline at end of file | ||