Hey Q! Make Me A Cool Desktop App

Welcome to the Hey Q! blog series where we build fun apps using Amazon Q Developer and learn about the code along the way! The scope of the sample apps created in this series are small 1-2 page applications that are fun, well designed and properly architected following best practices. And, the best part, the code is all open-source allowing you to grab it and use it for your apps! Here's the link to the completed open-source project: https://github.com/TheCodeTraveler/ProductivityPulse
This week we are building a Desktop App using WPF. For this app, I have a few goals:
1. The app must compile and run on Windows Desktop
2. The app must have a nice, highly polished, professional User Interface (UI)
3. The app must have a fun name that relates to its use
4. The code must be created by first using the latest version of the WPF template in the latest version of Visual Studio
5. The code must use the MVVM Architecture
6. The code must follow best practices
7. The code must use the most-recent versions for all dependencies
The Completed App
Note: If you're interested in learning more about prompt engineering and want to see the steps I used to guide Q Developer CLI, scroll down to the Appendix.
The completed app is nice! Its design isn't top-tier, but is certainly a UI that I would've spent hours creating and implementing.
The functionality, however, is lacking. As you can see in the animated GIF (below), I can add a new Task and I can delete created tasks. However, there is no mechanism for me to record how long my Task took to complete and there is no mechanism for me to mark a Task as completed.
Despite the functionality, the app does compile and run without crashing and it gives me a great starting point to add the missing functionality. In fact, this is the first app in the Hey Q! series that has compiled with no errors and has required no intervention from me to run which is great!

Appendix
For anyone out there who is interested in learning more about prompt engineering, this section contains the steps I followed to create this app.
Note: Responses from LLMs are non-deterministic which can result in different outputs despite asking the same question. You may receive slightly different responses from Q Developer when trying to replicate the same steps below.
I've already installed Q Developer CLI on my Mac, so let's begin by opening the macOS Terminal and begin chatting with Q using the following command:
q chat
Next, I will help Q out a bit by giving Q a profile. This reduces the scope of the datasets that Q will reference helps Q better understand the problem we are trying to solve together:
**Prompt**
Hey Q! You are a senior WPF Developer who is an expert in C# and always follows C# and Desktop best practices. You always reference the official C# documentation on https://learn.microsoft.com/dotnet/csharp/ when writing your code and you always reference the official Blazor documentation when writing your code https://learn.microsoft.com/dotnet/desktop/wpf/. You always write code that uses the recommended libraries, like .NET Aspire (https://www.nuget.org/profiles/aspire) and CommunityToolkit.Mvvm (https://www.nuget.org/packages/CommunityToolkit.Mvvm). The User Interface for your apps are always highly polished and professional, giving the appearance that it was created by a Senior Designer. In addition to the reference domains above, you are also allowed to reference the open-source NET runtime code on GitHub, https://github.com/dotnet/runtime, andthe open-source WPF code on GitHub https://github.com/dotnet/wpf.
Done! Now let's ask it to start building us an app and see what happens!
**Prompt**
Starting first using the latest version of the WPF Desktop App template in the latest version of Visual Studio, create a fun one-page desktop app that adheres to the following rules: 1. The app must compile and run on Windows Desktop, 2. The app must have a nice, highly polished, professional User Interface (UI), 3. The app must have a fun name that relates to its use, 4. The code must created by first using the latest version of the WPF template in the latest version of Visual Studio, 5. The code must use the MVVM Architecture, 6. The code must follow best practices, 7. The code must use the most-recent versions for all dependencies
**Response**
Perfect! Let's create a fun, professional WPF desktop application. I'll build a "Productivity Pulse" app - a sleek task and time tracker with a modern UI.
Let me create the complete application structure for you:
+ 1: <Project Sdk="Microsoft.NET.Sdk">
+ 2:
+ 3: <PropertyGroup>
+ 4: <OutputType>WinExe</OutputType>
+ 5: <TargetFramework>net9.0-windows</TargetFramework>
+ 6: <Nullable>enable</Nullable>
+ 7: <UseWPF>true</UseWPF>
+ 8: <ApplicationIcon>icon.ico</ApplicationIcon>
+ 9: <AssemblyTitle>Productivity Pulse</AssemblyTitle>
+ 10: <AssemblyDescription>A modern task and time tracking application</AssemblyDescription>
+ 11: <AssemblyVersion>1.0.0.0</AssemblyVersion>
+ 12: <FileVersion>1.0.0.0</FileVersion>
+ 13: </PropertyGroup>
+ 14:
+ 15: <ItemGroup>
+ 16: <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
+ 17: <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
+ 18: <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.7" />
+ 19: <PackageReference Include="System.Text.Json" Version="9.0.7" />
+ 20: </ItemGroup>
+ 21:
+ 22: </Project>
It has started by creating the CSPROJ file for us. Approved.
Next, it has created our Model:
+ 1: using System;
+ 2: using System.Text.Json.Serialization;
+ 3:
+ 4: namespace ProductivityPulse.Models;
+ 5:
+ 6: /// <summary>
+ 7: /// Represents a task item with timing capabilities
+ 8: /// </summary>
+ 9: public class TaskItem
+ 10: {
+ 11: public Guid Id { get; set; } = Guid.NewGuid();
+ 12:
+ 13: public string Title { get; set; } = string.Empty;
+ 14:
+ 15: public string Description { get; set; } = string.Empty;
+ 16:
+ 17: public DateTime CreatedAt { get; set; } = DateTime.Now;
+ 18:
+ 19: public DateTime? CompletedAt { get; set; }
+ 20:
+ 21: public TimeSpan TimeSpent { get; set; } = TimeSpan.Zero;
+ 22:
+ 23: public bool IsCompleted { get; set; }
+ 24:
+ 25: public bool IsRunning { get; set; }
+ 26:
+ 27: public TaskPriority Priority { get; set; } = TaskPriority.Medium;
+ 28:
+ 29: [JsonIgnore]
+ 30: public string FormattedTimeSpent =>
+ 31: $"{(int)TimeSpent.TotalHours:D2}:{TimeSpent.Minutes:D2}:{TimeSpent.Seconds:D2}";
+ 32:
+ 33: [JsonIgnore]
+ 34: public string PriorityColor => Priority switch
+ 35: {
+ 36: TaskPriority.High => "#FF6B6B",
+ 37: TaskPriority.Medium => "#4ECDC4",
+ 38: TaskPriority.Low => "#95E1D3",
+ 39: _ => "#4ECDC4"
+ 40: };
+ 41: }
+ 42:
+ 43: public enum TaskPriority
+ 44: {
+ 45: Low,
+ 46: Medium,
+ 47: High
+ 48: }
Approved.
Next, it has created our View Model:
+ 1: using System;
+ 2: using System.Collections.ObjectModel;
+ 3: using System.IO;
+ 4: using System.Linq;
+ 5: using System.Text.Json;
+ 6: using System.Threading.Tasks;
+ 7: using System.Windows.Threading;
+ 8: using CommunityToolkit.Mvvm.ComponentModel;
+ 9: using CommunityToolkit.Mvvm.Input;
+ 10: using ProductivityPulse.Models;
+ 11:
+ 12: namespace ProductivityPulse.ViewModels;
+ 13:
+ 14: /// <summary>
+ 15: /// Main ViewModel for the Productivity Pulse application
+ 16: /// </summary>
+ 17: public partial class MainViewModel : ObservableObject
+ 18: {
+ 19: private readonly DispatcherTimer _timer;
+ 20: private readonly string _dataFilePath;
+ 21:
+ 22: [ObservableProperty]
+ 23: private string _newTaskTitle = string.Empty;
+ 24:
+ 25: [ObservableProperty]
+ 26: private string _newTaskDescription = string.Empty;
+ 27:
+ 28: [ObservableProperty]
+ 29: private TaskPriority _newTaskPriority = TaskPriority.Medium;
+ 30:
+ 31: [ObservableProperty]
+ 32: private TaskItem? _selectedTask;
+ 33:
+ 34: [ObservableProperty]
+ 35: private string _totalTimeToday = "00:00:00";
+ 36:
+ 37: [ObservableProperty]
+ 38: private int _completedTasksToday;
+ 39:
+ 40: [ObservableProperty]
+ 41: private int _totalTasksToday;
+ 42:
+ 43: public ObservableCollection<TaskItem> Tasks { get; } = new();
+ 44:
+ 45: public MainViewModel()
+ 46: {
+ 47: _dataFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ 48: "ProductivityPulse", "tasks.json");
+ 49:
+ 50: _timer = new DispatcherTimer
+ 51: {
+ 52: Interval = TimeSpan.FromSeconds(1)
+ 53: };
+ 54: _timer.Tick += Timer_Tick;
+ 55: _timer.Start();
+ 56:
+ 57: LoadTasksAsync();
+ 58: UpdateStatistics();
+ 59: }
+ 60:
+ 61: private void Timer_Tick(object? sender, EventArgs e)
+ 62: {
+ 63: var runningTasks = Tasks.Where(t => t.IsRunning).ToList();
+ 64:
+ 65: foreach (var task in runningTasks)
+ 66: {
+ 67: task.TimeSpent = task.TimeSpent.Add(TimeSpan.FromSeconds(1));
+ 68: OnPropertyChanged(nameof(Tasks));
+ 69: }
+ 70:
+ 71: UpdateStatistics();
+ 72: }
+ 73:
+ 74: [RelayCommand]
+ 75: private async Task AddTaskAsync()
+ 76: {
+ 77: if (string.IsNullOrWhiteSpace(NewTaskTitle))
+ 78: return;
+ 79:
+ 80: var newTask = new TaskItem
+ 81: {
+ 82: Title = NewTaskTitle.Trim(),
+ 83: Description = NewTaskDescription.Trim(),
+ 84: Priority = NewTaskPriority
+ 85: };
+ 86:
+ 87: Tasks.Add(newTask);
+ 88:
+ 89: // Clear form
+ 90: NewTaskTitle = string.Empty;
+ 91: NewTaskDescription = string.Empty;
+ 92: NewTaskPriority = TaskPriority.Medium;
+ 93:
+ 94: UpdateStatistics();
+ 95: await SaveTasksAsync();
+ 96: }
+ 97:
+ 98: [RelayCommand]
+ 99: private async Task ToggleTaskTimerAsync(TaskItem task)
+ 100: {
+ 101: if (task.IsCompleted)
+ 102: return;
+ 103:
+ 104: task.IsRunning = !task.IsRunning;
+ 105:
+ 106: // Stop other running tasks (only one can run at a time)
+ 107: if (task.IsRunning)
+ 108: {
+ 109: foreach (var otherTask in Tasks.Where(t => t != task && t.IsRunning))
+ 110: {
+ 111: otherTask.IsRunning = false;
+ 112: }
+ 113: }
+ 114:
+ 115: OnPropertyChanged(nameof(Tasks));
+ 116: await SaveTasksAsync();
+ 117: }
+ 118:
+ 119: [RelayCommand]
+ 120: private async Task CompleteTaskAsync(TaskItem task)
+ 121: {
+ 122: task.IsCompleted = true;
+ 123: task.IsRunning = false;
+ 124: task.CompletedAt = DateTime.Now;
+ 125:
+ 126: OnPropertyChanged(nameof(Tasks));
+ 127: UpdateStatistics();
+ 128: await SaveTasksAsync();
+ 129: }
+ 130:
+ 131: [RelayCommand]
+ 132: private async Task DeleteTaskAsync(TaskItem task)
+ 133: {
+ 134: Tasks.Remove(task);
+ 135: UpdateStatistics();
+ 136: await SaveTasksAsync();
+ 137: }
+ 138:
+ 139: [RelayCommand]
+ 140: private async Task ResetTaskAsync(TaskItem task)
+ 141: {
+ 142: task.IsCompleted = false;
+ 143: task.IsRunning = false;
+ 144: task.CompletedAt = null;
+ 145: task.TimeSpent = TimeSpan.Zero;
+ 146:
+ 147: OnPropertyChanged(nameof(Tasks));
+ 148: UpdateStatistics();
+ 149: await SaveTasksAsync();
+ 150: }
+ 151:
+ 152: private void UpdateStatistics()
+ 153: {
+ 154: var today = DateTime.Today;
+ 155: var todaysTasks = Tasks.Where(t => t.CreatedAt.Date == today).ToList();
+ 156:
+ 157: TotalTasksToday = todaysTasks.Count;
+ 158: CompletedTasksToday = todaysTasks.Count(t => t.IsCompleted);
+ 159:
+ 160: var totalTime = Tasks.Where(t => t.CreatedAt.Date == today)
+ 161: .Aggregate(TimeSpan.Zero, (sum, task) => sum.Add(task.TimeSpent));
+ 162:
+ 163: TotalTimeToday = $"{(int)totalTime.TotalHours:D2}:{totalTime.Minutes:D2}:{totalTime.Seconds:D2}";
+ 164: }
+ 165:
+ 166: private async Task SaveTasksAsync()
+ 167: {
+ 168: try
+ 169: {
+ 170: var directory = Path.GetDirectoryName(_dataFilePath);
+ 171: if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
+ 172: {
+ 173: Directory.CreateDirectory(directory);
+ 174: }
+ 175:
+ 176: var json = JsonSerializer.Serialize(Tasks, new JsonSerializerOptions
+ 177: {
+ 178: WriteIndented = true
+ 179: });
+ 180:
+ 181: await File.WriteAllTextAsync(_dataFilePath, json);
+ 182: }
+ 183: catch (Exception ex)
+ 184: {
+ 185: // In a production app, you'd want proper logging here
+ 186: System.Diagnostics.Debug.WriteLine($"Error saving tasks: {ex.Message}");
+ 187: }
+ 188: }
+ 189:
+ 190: private async Task LoadTasksAsync()
+ 191: {
+ 192: try
+ 193: {
+ 194: if (!File.Exists(_dataFilePath))
+ 195: return;
+ 196:
+ 197: var json = await File.ReadAllTextAsync(_dataFilePath);
+ 198: var tasks = JsonSerializer.Deserialize<TaskItem[]>(json);
+ 199:
+ 200: if (tasks != null)
+ 201: {
+ 202: Tasks.Clear();
+ 203: foreach (var task in tasks)
+ 204: {
+ 205: // Reset running state on app startup
+ 206: task.IsRunning = false;
+ 207: Tasks.Add(task);
+ 208: }
+ 209: }
+ 210: }
+ 211: catch (Exception ex)
+ 212: {
+ 213: // In a production app, you'd want proper logging here
+ 214: System.Diagnostics.Debug.WriteLine($"Error loading tasks: {ex.Message}");
+ 215: }
+ 216: }
+ 217: }
Next, it has created MainWindow.xaml:
+ 1: <Window x:Class="ProductivityPulse.MainWindow"
+ 2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ 3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ 4: xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ 5: xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ 6: xmlns:models="clr-namespace:ProductivityPulse.Models"
+ 7: mc:Ignorable="d"
+ 8: Title="Productivity Pulse"
+ 9: Height="800"
+ 10: Width="1200"
+ 11: MinHeight="600"
+ 12: MinWidth="800"
+ 13: WindowStartupLocation="CenterScreen"
+ 14: Background="#F8F9FA">
+ 15:
+ 16: <Window.Resources>
+ 17: <!-- Modern Button Style -->
+ 18: <Style x:Key="ModernButtonStyle" TargetType="Button">
+ 19: <Setter Property="Background" Value="#007ACC"/>
+ 20: <Setter Property="Foreground" Value="White"/>
+ 21: <Setter Property="BorderThickness" Value="0"/>
+ 22: <Setter Property="Padding" Value="16,8"/>
+ 23: <Setter Property="Margin" Value="4"/>
+ 24: <Setter Property="FontWeight" Value="SemiBold"/>
+ 25: <Setter Property="Cursor" Value="Hand"/>
+ 26: <Setter Property="Template">
+ 27: <Setter.Value>
+ 28: <ControlTemplate TargetType="Button">
+ 29: <Border Background="{TemplateBinding Background}"
+ 30: CornerRadius="6"
+ 31: Padding="{TemplateBinding Padding}">
+ 32: <ContentPresenter HorizontalAlignment="Center"
+ 33: VerticalAlignment="Center"/>
+ 34: </Border>
+ 35: <ControlTemplate.Triggers>
+ 36: <Trigger Property="IsMouseOver" Value="True">
+ 37: <Setter Property="Background" Value="#005A9E"/>
+ 38: </Trigger>
+ 39: <Trigger Property="IsPressed" Value="True">
+ 40: <Setter Property="Background" Value="#004578"/>
+ 41: </Trigger>
+ 42: </ControlTemplate.Triggers>
+ 43: </ControlTemplate>
+ 44: </Setter.Value>
+ 45: </Setter>
+ 46: </Style>
+ 47:
+ 48: <!-- Card Style -->
+ 49: <Style x:Key="CardStyle" TargetType="Border">
+ 50: <Setter Property="Background" Value="White"/>
+ 51: <Setter Property="CornerRadius" Value="12"/>
+ 52: <Setter Property="Padding" Value="20"/>
+ 53: <Setter Property="Margin" Value="8"/>
+ 54: <Setter Property="Effect">
+ 55: <Setter.Value>
+ 56: <DropShadowEffect Color="#E0E0E0"
+ 57: Direction="270"
+ 58: ShadowDepth="2"
+ 59: BlurRadius="8"
+ 60: Opacity="0.3"/>
+ 61: </Setter.Value>
+ 62: </Setter>
+ 63: </Style>
+ 64:
+ 65: <!-- Task Item Style -->
+ 66: <Style x:Key="TaskItemStyle" TargetType="Border">
+ 67: <Setter Property="Background" Value="White"/>
+ 68: <Setter Property="CornerRadius" Value="8"/>
+ 69: <Setter Property="Padding" Value="16"/>
+ 70: <Setter Property="Margin" Value="0,4"/>
+ 71: <Setter Property="BorderThickness" Value="1"/>
+ 72: <Setter Property="BorderBrush" Value="#E9ECEF"/>
+ 73: <Setter Property="Effect">
+ 74: <Setter.Value>
+ 75: <DropShadowEffect Color="#E0E0E0"
+ 76: Direction="270"
+ 77: ShadowDepth="1"
+ 78: BlurRadius="4"
+ 79: Opacity="0.2"/>
+ 80: </Setter.Value>
+ 81: </Setter>
+ 82: </Style>
+ 83:
+ 84: <!-- Icon Button Style -->
+ 85: <Style x:Key="IconButtonStyle" TargetType="Button">
+ 86: <Setter Property="Background" Value="Transparent"/>
+ 87: <Setter Property="BorderThickness" Value="0"/>
+ 88: <Setter Property="Padding" Value="8"/>
+ 89: <Setter Property="Margin" Value="2"/>
+ 90: <Setter Property="Cursor" Value="Hand"/>
+ 91: <Setter Property="Width" Value="32"/>
+ 92: <Setter Property="Height" Value="32"/>
+ 93: <Setter Property="Template">
+ 94: <Setter.Value>
+ 95: <ControlTemplate TargetType="Button">
+ 96: <Border Background="{TemplateBinding Background}"
+ 97: CornerRadius="4"
+ 98: Padding="{TemplateBinding Padding}">
+ 99: <ContentPresenter HorizontalAlignment="Center"
+ 100: VerticalAlignment="Center"/>
+ 101: </Border>
+ 102: <ControlTemplate.Triggers>
+ 103: <Trigger Property="IsMouseOver" Value="True">
+ 104: <Setter Property="Background" Value="#F8F9FA"/>
+ 105: </Trigger>
+ 106: </ControlTemplate.Triggers>
+ 107: </ControlTemplate>
+ 108: </Setter.Value>
+ 109: </Setter>
+ 110: </Style>
+ 111: </Window.Resources>
+ 112:
+ 113: <Grid>
+ 114: <Grid.ColumnDefinitions>
+ 115: <ColumnDefinition Width="400"/>
+ 116: <ColumnDefinition Width="*"/>
+ 117: </Grid.ColumnDefinitions>
+ 118:
+ 119: <!-- Left Panel - Task Creation and Statistics -->
+ 120: <Border Grid.Column="0" Background="#F1F3F4" Padding="20">
+ 121: <StackPanel>
+ 122: <!-- Header -->
+ 123: <TextBlock Text="Productivity Pulse"
+ 124: FontSize="28"
+ 125: FontWeight="Bold"
+ 126: Foreground="#2C3E50"
+ 127: Margin="0,0,0,20"/>
+ 128:
+ 129: <!-- Statistics Card -->
+ 130: <Border Style="{StaticResource CardStyle}">
+ 131: <StackPanel>
+ 132: <TextBlock Text="Today's Progress"
+ 133: FontSize="18"
+ 134: FontWeight="SemiBold"
+ 135: Foreground="#2C3E50"
+ 136: Margin="0,0,0,12"/>
+ 137:
+ 138: <Grid>
+ 139: <Grid.ColumnDefinitions>
+ 140: <ColumnDefinition Width="*"/>
+ 141: <ColumnDefinition Width="*"/>
+ 142: </Grid.ColumnDefinitions>
+ 143:
+ 144: <StackPanel Grid.Column="0">
+ 145: <TextBlock Text="Tasks Completed"
+ 146: FontSize="12"
+ 147: Foreground="#6C757D"/>
+ 148: <TextBlock Text="{Binding CompletedTasksToday}"
+ 149: FontSize="24"
+ 150: FontWeight="Bold"
+ 151: Foreground="#28A745"/>
+ 152: </StackPanel>
+ 153:
+ 154: <StackPanel Grid.Column="1">
+ 155: <TextBlock Text="Time Tracked"
+ 156: FontSize="12"
+ 157: Foreground="#6C757D"/>
+ 158: <TextBlock Text="{Binding TotalTimeToday}"
+ 159: FontSize="24"
+ 160: FontWeight="Bold"
+ 161: Foreground="#007ACC"/>
+ 162: </StackPanel>
+ 163: </Grid>
+ 164: </StackPanel>
+ 165: </Border>
+ 166:
+ 167: <!-- Add Task Card -->
+ 168: <Border Style="{StaticResource CardStyle}">
+ 169: <StackPanel>
+ 170: <TextBlock Text="Add New Task"
+ 171: FontSize="18"
+ 172: FontWeight="SemiBold"
+ 173: Foreground="#2C3E50"
+ 174: Margin="0,0,0,12"/>
+ 175:
+ 176: <TextBox Text="{Binding NewTaskTitle, UpdateSourceTrigger=PropertyChanged}"
+ 177: Padding="12"
+ 178: FontSize="14"
+ 179: Margin="0,0,0,8"
+ 180: BorderBrush="#DEE2E6"
+ 181: BorderThickness="1">
+ 182: <TextBox.Style>
+ 183: <Style TargetType="TextBox">
+ 184: <Setter Property="Template">
+ 185: <Setter.Value>
+ 186: <ControlTemplate TargetType="TextBox">
+ 187: <Border Background="White"
+ 188: BorderBrush="{TemplateBinding BorderBrush}"
+ 189: BorderThickness="{TemplateBinding BorderThickness}"
+ 190: CornerRadius="6"
+ 191: Padding="{TemplateBinding Padding}">
+ 192: <ScrollViewer x:Name="PART_ContentHost"/>
+ 193: </Border>
+ 194: </ControlTemplate>
+ 195: </Setter.Value>
+ 196: </Setter>
+ 197: </Style>
+ 198: </TextBox.Style>
+ 199: </TextBox>
+ 200:
+ 201: <TextBox Text="{Binding NewTaskDescription, UpdateSourceTrigger=PropertyChanged}"
+ 202: Padding="12"
+ 203: FontSize="14"
+ 204: Height="80"
+ 205: TextWrapping="Wrap"
+ 206: AcceptsReturn="True"
+ 207: VerticalScrollBarVisibility="Auto"
+ 208: Margin="0,0,0,8"
+ 209: BorderBrush="#DEE2E6"
+ 210: BorderThickness="1">
+ 211: <TextBox.Style>
+ 212: <Style TargetType="TextBox">
+ 213: <Setter Property="Template">
+ 214: <Setter.Value>
+ 215: <ControlTemplate TargetType="TextBox">
+ 216: <Border Background="White"
+ 217: BorderBrush="{TemplateBinding BorderBrush}"
+ 218: BorderThickness="{TemplateBinding BorderThickness}"
+ 219: CornerRadius="6"
+ 220: Padding="{TemplateBinding Padding}">
+ 221: <ScrollViewer x:Name="PART_ContentHost"/>
+ 222: </Border>
+ 223: </ControlTemplate>
+ 224: </Setter.Value>
+ 225: </Setter>
+ 226: </Style>
+ 227: </TextBox.Style>
+ 228: </TextBox>
+ 229:
+ 230: <ComboBox SelectedItem="{Binding NewTaskPriority}"
+ 231: Padding="12"
+ 232: FontSize="14"
+ 233: Margin="0,0,0,12"
+ 234: BorderBrush="#DEE2E6"
+ 235: BorderThickness="1">
+ 236: <ComboBox.Items>
+ 237: <models:TaskPriority>Low</models:TaskPriority>
+ 238: <models:TaskPriority>Medium</models:TaskPriority>
+ 239: <models:TaskPriority>High</models:TaskPriority>
+ 240: </ComboBox.Items>
+ 241: </ComboBox>
+ 242:
+ 243: <Button Content="Add Task"
+ 244: Style="{StaticResource ModernButtonStyle}"
+ 245: Command="{Binding AddTaskCommand}"
+ 246: HorizontalAlignment="Stretch"/>
+ 247: </StackPanel>
+ 248: </Border>
+ 249: </StackPanel>
+ 250: </Border>
+ 251:
+ 252: <!-- Right Panel - Task List -->
+ 253: <Border Grid.Column="1" Background="White" Padding="20">
+ 254: <Grid>
+ 255: <Grid.RowDefinitions>
+ 256: <RowDefinition Height="Auto"/>
+ 257: <RowDefinition Height="*"/>
+ 258: </Grid.RowDefinitions>
+ 259:
+ 260: <!-- Task List Header -->
+ 261: <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,20">
+ 262: <TextBlock Text="Active Tasks"
+ 263: FontSize="24"
+ 264: FontWeight="SemiBold"
+ 265: Foreground="#2C3E50"/>
+ 266: <TextBlock Text="{Binding Tasks.Count, StringFormat='({0})'}"
+ 267: FontSize="18"
+ 268: Foreground="#6C757D"
+ 269: Margin="8,4,0,0"/>
+ 270: </StackPanel>
+ 271:
+ 272: <!-- Task List -->
+ 273: <ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
+ 274: <ItemsControl ItemsSource="{Binding Tasks}">
+ 275: <ItemsControl.ItemTemplate>
+ 276: <DataTemplate>
+ 277: <Border Style="{StaticResource TaskItemStyle}">
+ 278: <Grid>
+ 279: <Grid.ColumnDefinitions>
+ 280: <ColumnDefinition Width="8"/>
+ 281: <ColumnDefinition Width="*"/>
+ 282: <ColumnDefinition Width="Auto"/>
+ 283: </Grid.ColumnDefinitions>
+ 284:
+ 285: <!-- Priority Indicator -->
+ 286: <Rectangle Grid.Column="0"
+ 287: Fill="{Binding PriorityColor}"
+ 288: RadiusX="4"
+ 289: RadiusY="4"/>
+ 290:
+ 291: <!-- Task Content -->
+ 292: <StackPanel Grid.Column="1" Margin="16,0">
+ 293: <TextBlock Text="{Binding Title}"
+ 294: FontSize="16"
+ 295: FontWeight="SemiBold"
+ 296: Foreground="#2C3E50">
+ 297: <TextBlock.Style>
+ 298: <Style TargetType="TextBlock">
+ 299: <Style.Triggers>
+ 300: <DataTrigger Binding="{Binding IsCompleted}" Value="True">
+ 301: <Setter Property="TextDecorations" Value="Strikethrough"/>
+ 302: <Setter Property="Foreground" Value="#6C757D"/>
+ 303: </DataTrigger>
+ 304: </Style.Triggers>
+ 305: </Style>
+ 306: </TextBlock.Style>
+ 307: </TextBlock>
+ 308:
+ 309: <TextBlock Text="{Binding Description}"
+ 310: FontSize="14"
+ 311: Foreground="#6C757D"
+ 312: TextWrapping="Wrap"
+ 313: Margin="0,4,0,8"
+ 314: Visibility="{Binding Description, Converter={x:Static local:StringToVisibilityConverter.Instance}}"/>
+ 315:
+ 316: <StackPanel Orientation="Horizontal">
+ 317: <TextBlock Text="⏱️" FontSize="14" Margin="0,0,4,0"/>
+ 318: <TextBlock Text="{Binding FormattedTimeSpent}"
+ 319: FontSize="14"
+ 320: FontWeight="SemiBold"
+ 321: Foreground="#007ACC"/>
+ 322:
+ 323: <TextBlock Text="🔥"
+ 324: FontSize="14"
+ 325: Margin="16,0,4,0"
+ 326: Visibility="{Binding IsRunning, Converter={x:Static local:BooleanToVisibilityConverter.Instance}}"/>
+ 327: <TextBlock Text="RUNNING"
+ 328: FontSize="12"
+ 329: FontWeight="Bold"
+ 330: Foreground="#FF6B6B"
+ 331: Visibility="{Binding IsRunning, Converter={x:Static local:BooleanToVisibilityConverter.Instance}}"/>
+ 332:
+ 333: <TextBlock Text="✅"
+ 334: FontSize="14"
+ 335: Margin="16,0,4,0"
+ 336: Visibility="{Binding IsCompleted, Converter={x:Static local:BooleanToVisibilityConverter.Instance}}"/>
+ 337: <TextBlock Text="COMPLETED"
+ 338: FontSize="12"
+ 339: FontWeight="Bold"
+ 340: Foreground="#28A745"
+ 341: Visibility="{Binding IsCompleted, Converter={x:Static local:BooleanToVisibilityConverter.Instance}}"/>
+ 342: </StackPanel>
+ 343: </StackPanel>
+ 344:
+ 345: <!-- Action Buttons -->
+ 346: <StackPanel Grid.Column="2" Orientation="Horizontal">
+ 347: <Button Content="▶️"
+ 348: Style="{StaticResource IconButtonStyle}"
+ 349: Command="{Binding DataContext.ToggleTaskTimerCommand, RelativeSource={RelativeSource AncestorType=Window}}"
+ 350: CommandParameter="{Binding}"
+ 351: ToolTip="Start/Stop Timer"
+ 352: Visibility="{Binding IsCompleted, Converter={x:Static local:InverseBooleanToVisibilityConverter.Instance}}"/>
+ 353:
+ 354: <Button Content="✅"
+ 355: Style="{StaticResource IconButtonStyle}"
+ 356: Command="{Binding DataContext.CompleteTaskCommand, RelativeSource={RelativeSource AncestorType=Window}}"
+ 357: CommandParameter="{Binding}"
+ 358: ToolTip="Mark Complete"
+ 359: Visibility="{Binding IsCompleted, Converter={x:Static local:InverseBooleanToVisibilityConverter.Instance}}"/>
+ 360:
+ 361: <Button Content="🔄"
+ 362: Style="{StaticResource IconButtonStyle}"
+ 363: Command="{Binding DataContext.ResetTaskCommand, RelativeSource={RelativeSource AncestorType=Window}}"
+ 364: CommandParameter="{Binding}"
+ 365: ToolTip="Reset Task"
+ 366: Visibility="{Binding IsCompleted, Converter={x:Static local:BooleanToVisibilityConverter.Instance}}"/>
+ 367:
+ 368: <Button Content="🗑️"
+ 369: Style="{StaticResource IconButtonStyle}"
+ 370: Command="{Binding DataContext.DeleteTaskCommand, RelativeSource={RelativeSource AncestorType=Window}}"
+ 371: CommandParameter="{Binding}"
+ 372: ToolTip="Delete Task"/>
+ 373: </StackPanel>
+ 374: </Grid>
+ 375: </Border>
+ 376: </DataTemplate>
+ 377: </ItemsControl.ItemTemplate>
+ 378: </ItemsControl>
+ 379: </ScrollViewer>
+ 380: </Grid>
+ 381: </Border>
+ 382: </Grid>
+ 383: </Window>
Next, it created the code-behind file, MainWindow.xaml.cs:
+ 1: using System.Windows;
+ 2: using ProductivityPulse.ViewModels;
+ 3:
+ 4: namespace ProductivityPulse;
+ 5:
+ 6: /// <summary>
+ 7: /// Interaction logic for MainWindow.xaml
+ 8: /// </summary>
+ 9: public partial class MainWindow : Window
+ 10: {
+ 11: public MainWindow()
+ 12: {
+ 13: InitializeComponent();
+ 14: DataContext = new MainViewModel();
+ 15: }
+ 16: }
Next, it has created ValueConverters
:
+ 1: using System;
+ 2: using System.Globalization;
+ 3: using System.Windows;
+ 4: using System.Windows.Data;
+ 5:
+ 6: namespace ProductivityPulse;
+ 7:
+ 8: /// <summary>
+ 9: /// Converts string to Visibility (Collapsed if null or empty)
+ 10: /// </summary>
+ 11: public class StringToVisibilityConverter : IValueConverter
+ 12: {
+ 13: private static readonly Lazy<StringToVisibilityConverter> _instance = new(() => new StringToVisibilityConverter());
+ 14: public static StringToVisibilityConverter Instance => _instance.Value;
+ 15:
+ 16: private StringToVisibilityConverter() { }
+ 14:
+ 15: public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ 16: {
+ 17: return string.IsNullOrWhiteSpace(value?.ToString()) ? Visibility.Collapsed : Visibility.Visible;
+ 18: }
+ 19:
+ 20: public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ 21: {
+ 22: throw new NotImplementedException();
+ 23: }
+ 24: }
+ 25:
+ 26: /// <summary>
+ 27: /// Converts boolean to Visibility
+ 28: /// </summary>
+ 29: public class BooleanToVisibilityConverter : IValueConverter
+ 30: {
+ 34: private static readonly Lazy<BooleanToVisibilityConverter> _instance = new(() => new BooleanToVisibilityConverter());
+ 35: public static BooleanToVisibilityConverter Instance => _instance.Value;
+ 36:
+ 37: private BooleanToVisibilityConverter() { }
+ 32:
+ 33: public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ 34: {
+ 35: return value is true ? Visibility.Visible : Visibility.Collapsed;
+ 36: }
+ 37:
+ 38: public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ 39: {
+ 40: return value is Visibility.Visible;
+ 41: }
+ 42: }
+ 43:
+ 44: /// <summary>
+ 45: /// Converts boolean to Visibility (inverted)
+ 46: /// </summary>
+ 47: public class InverseBooleanToVisibilityConverter : IValueConverter
+ 48: {
+ 55: private static readonly Lazy<InverseBooleanToVisibilityConverter> _instance = new(() => new InverseBooleanToVisibilityConverter());
+ 56: public static InverseBooleanToVisibilityConverter Instance => _instance.Value;
+ 57:
+ 58: private InverseBooleanToVisibilityConverter() { }
+ 50:
+ 51: public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+ 52: {
+ 53: return value is true ? Visibility.Collapsed : Visibility.Visible;
+ 54: }
+ 55:
+ 56: public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+ 57: {
+ 58: return value is Visibility.Collapsed;
+ 59: }
+ 60: }
Next, it created our App.xaml file:
+ 1: <Application x:Class="ProductivityPulse.App"
+ 2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ 3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ 4: StartupUri="MainWindow.xaml">
+ 5: <Application.Resources>
+ 6: <!-- Global application resources can be added here -->
+ 7: </Application.Resources>
+ 8: </Application>
Finally, it created the code-behind file App.xaml.cs:
+ 1: using System.Windows;
+ 2:
+ 3: namespace ProductivityPulse;
+ 4:
+ 5: /// <summary>
+ 6: /// Interaction logic for App.xaml
+ 7: /// </summary>
+ 8: public partial class App : Application
+ 9: {
+ 10: protected override void OnStartup(StartupEventArgs e)
+ 11: {
+ 12: base.OnStartup(e);
+ 13:
+ 14: // Set up global exception handling
+ 15: DispatcherUnhandledException += (sender, args) =>
+ 16: {
+ 17: MessageBox.Show($"An unexpected error occurred: {args.Exception.Message}",
+ 18: "Productivity Pulse Error",
+ 19: MessageBoxButton.OK,
+ 20: MessageBoxImage.Error);
+ 21: args.Handled = true;
+ 22: };
+ 23: }
+ 24: }