Snoopy hace 4 años
padre
commit
922f8570b0

+ 114 - 0
App.config

@@ -0,0 +1,114 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+    <configSections>
+        <sectionGroup name="userSettings" type="System.Configuration.UserSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
+            <section name="Phihong_EVSE_UI_Tool.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" allowExeDefinition="MachineToLocalUser" requirePermission="false" />
+        </sectionGroup>
+    </configSections>
+    <startup> 
+        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
+    </startup>
+    <userSettings>
+        <Phihong_EVSE_UI_Tool.Properties.Settings>
+            <setting name="IsInitialDefault" serializeAs="String">
+                <value>True</value>
+            </setting>
+            <setting name="InitialCustomImgPath" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="InitialCustomImgPath_org" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="IsIdleDefault" serializeAs="String">
+                <value>True</value>
+            </setting>
+            <setting name="IdleCustomImgPath" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="IdleCustomImgPath_org" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="IsVerifyDefault" serializeAs="String">
+                <value>True</value>
+            </setting>
+            <setting name="VerifyCustomImgPath" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="VerifyCustomImgPath_org" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="IsVerifyOkDefault" serializeAs="String">
+                <value>True</value>
+            </setting>
+            <setting name="VerifyOkCustomImgPath" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="VerifyOkCustomImgPath_org" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="IsVerifyFailDefault" serializeAs="String">
+                <value>True</value>
+            </setting>
+            <setting name="VerifyFailCustomImgPath" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="VerifyFailCustomImgPath_org" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="IsPlugDefault" serializeAs="String">
+                <value>True</value>
+            </setting>
+            <setting name="PlugCustomImgPath" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="PlugCustomImgPath_org" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="IsPreChargeDefault" serializeAs="String">
+                <value>True</value>
+            </setting>
+            <setting name="PreChargeCustomImgPath" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="PreChargeCustomImgPath_org" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="IsChargingDefault" serializeAs="String">
+                <value>True</value>
+            </setting>
+            <setting name="ChargingCustomImgPath" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="ChargingCustomImgPath_org" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="IsCompleteDefault" serializeAs="String">
+                <value>True</value>
+            </setting>
+            <setting name="CompleteCustomImgPath" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="CompleteCustomImgPath_org" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="IsMaintainDefault" serializeAs="String">
+                <value>True</value>
+            </setting>
+            <setting name="MaintainCustomImgPath" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="MaintainCustomImgPath_org" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="IsLogoDefault" serializeAs="String">
+                <value>True</value>
+            </setting>
+            <setting name="LogoCustomImgPath" serializeAs="String">
+                <value />
+            </setting>
+            <setting name="LogoCustomImgPath_org" serializeAs="String">
+                <value />
+            </setting>
+        </Phihong_EVSE_UI_Tool.Properties.Settings>
+    </userSettings>
+</configuration>

+ 18 - 0
App.xaml

@@ -0,0 +1,18 @@
+<Application x:Class="Phihong_EVSE_UI_Tool.App"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:local="clr-namespace:Phihong_EVSE_UI_Tool"
+             StartupUri="MainWindow.xaml">
+    <Application.Resources>
+        <ResourceDictionary>
+            <ResourceDictionary.MergedDictionaries>
+                <!-- FluentWPF Controls -->
+                <ResourceDictionary Source="pack://application:,,,/FluentWPF;component/Styles/Controls.xaml"/>
+
+                <!-- Material Design Controls -->
+                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml"/>
+                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml"/>
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
+    </Application.Resources>
+</Application>

+ 27 - 0
App.xaml.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Configuration;
+using System.Data;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace Phihong_EVSE_UI_Tool
+{
+    /// <summary>
+    /// App.xaml 的互動邏輯
+    /// </summary>
+    public partial class App : Application
+    {
+        protected override void OnStartup(StartupEventArgs e)
+        {
+            base.OnStartup(e);
+
+            Directory.CreateDirectory(Environment.CurrentDirectory + @"\Output");
+            
+            DirectoryInfo di = Directory.CreateDirectory(Environment.CurrentDirectory + @"\CustomTemp");
+            di.Attributes = FileAttributes.Directory | FileAttributes.Hidden;
+        }
+    }
+}

+ 77 - 0
AuthenticationUC.xaml

@@ -0,0 +1,77 @@
+<UserControl x:Class="Phihong_EVSE_UI_Tool.AuthenticationUC"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Phihong_EVSE_UI_Tool"
+             xmlns:fd="clr-namespace:SourceChord.FluentWPF;assembly=FluentWPF"
+             mc:Ignorable="d" 
+             d:DesignHeight="1070" d:DesignWidth="880" FontSize="15" FontFamily="Segoe UI">
+    <UserControl.Resources>
+        <BooleanToVisibilityConverter x:Key="b2v"/>
+    </UserControl.Resources>
+    <Grid fd:PointerTracker.Enabled="True">
+        <ScrollViewer x:Name="uxContentScrollViewer" HorizontalContentAlignment="Center" PanningMode="VerticalOnly" IsDeferredScrollingEnabled="False" DataContext="{Binding}">
+            <StackPanel>
+                <!-- Verify -->
+                <TextBlock Text="Verify" FontSize="24" HorizontalAlignment="Left" Margin="22,6,0,8"/>
+                <TextBlock Text="Background picture for the Verify status (800x480)" HorizontalAlignment="Left" Margin="22,4,0,12"/>
+                <Image x:Name="uxVerifyImage" Source="/Background/2_verify.bmp" HorizontalAlignment="Left" Margin="22,0,0,12" Height="192" Width="320" RenderOptions.BitmapScalingMode="HighQuality"/>
+                <StackPanel Orientation="Horizontal" Margin="22,8,370,12">
+                    <RadioButton x:Name="uxVerifyDefaultRadioButton" Tag="Verify" GroupName="verifySelect" Margin="0,0,36,0" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxDefaultRadioButton_Checked">Default</RadioButton>
+                    <RadioButton x:Name="uxVerifyCustomRadioButton" Tag="Verify" GroupName="verifySelect" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxCustomRadioButton_Checked">Customized</RadioButton>
+                </StackPanel>
+                <StackPanel x:Name="uxVerifyCustomStackPanel" Orientation="Horizontal" Margin="22,0,450,12" 
+                            Visibility="{Binding IsChecked,ElementName=uxVerifyCustomRadioButton,Converter={StaticResource b2v}}">
+                    <Button Tag="Verify" Content="Browse" HorizontalAlignment="Left" Width="90" Height="35" 
+                            Style="{StaticResource ButtonRevealStyle}" Click="uxBrowseButton_Click"/>
+                    <TextBox x:Name="uxVerifyExploreTextBox" VerticalContentAlignment="Center" Margin="1,0,0,0" Width="270" Height="35" 
+                             Style="{StaticResource TextBoxRevealStyle}" Text="Please select an image file" IsReadOnly="True"/>
+                </StackPanel>
+                <!-- Idle -->
+                <TextBlock Text="Verification Successful" FontSize="24" HorizontalAlignment="Left" Margin="22,18,0,8"/>
+                <TextBlock Text="Background picture for Verification Successful (800x480)" HorizontalAlignment="Left" Margin="22,4,0,12"/>
+                <Image x:Name="uxVerifyOkImage" Source="/Background/3_carChkok.bmp" HorizontalAlignment="Left" Margin="22,0,0,12" Height="192" Width="320" RenderOptions.BitmapScalingMode="HighQuality"/>
+                <StackPanel Orientation="Horizontal" Margin="22,8,370,12">
+                    <RadioButton x:Name="uxVerifyOkDefaultRadioButton" Tag="VerifyOk" GroupName="verifyOkSelect" Margin="0,0,36,0" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxDefaultRadioButton_Checked">Default</RadioButton>
+                    <RadioButton x:Name="uxVerifyOkCustomRadioButton" Tag="VerifyOk" GroupName="verifyOkSelect" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxCustomRadioButton_Checked">Customized</RadioButton>
+                </StackPanel>
+                <StackPanel x:Name="uxVerifyOkCustomStackPanel" Orientation="Horizontal" Margin="22,0,450,12" 
+                            Visibility="{Binding IsChecked,ElementName=uxVerifyOkCustomRadioButton,Converter={StaticResource b2v}}">
+                    <Button Tag="VerifyOk" Content="Browse" HorizontalAlignment="Left" Width="90" Height="35" 
+                            Style="{StaticResource ButtonRevealStyle}" Click="uxBrowseButton_Click"/>
+                    <TextBox x:Name="uxVerifyOkExploreTextBox" VerticalContentAlignment="Center" Margin="1,0,0,0" Width="270" Height="35" 
+                             Style="{StaticResource TextBoxRevealStyle}" Text="Please select an image file" IsReadOnly="True"/>
+                </StackPanel>
+                <!-- Verification Fail -->
+                <TextBlock Text="Verification Fail" FontSize="24" HorizontalAlignment="Left" Margin="22,18,0,8"/>
+                <TextBlock Text="Background picture for Verification Fail (800x480)" HorizontalAlignment="Left" Margin="22,4,0,12"/>
+                <Image x:Name="uxVerifyFailImage" Source="/Background/4_chkfail.bmp" HorizontalAlignment="Left" Margin="22,0,0,12" Height="192" Width="320" RenderOptions.BitmapScalingMode="HighQuality"/>
+                <StackPanel Orientation="Horizontal" Margin="22,8,370,12">
+                    <RadioButton x:Name="uxVerifyFailDefaultRadioButton" Tag="VerifyFail" GroupName="verifyFailSelect" Margin="0,0,36,0" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxDefaultRadioButton_Checked">Default</RadioButton>
+                    <RadioButton x:Name="uxVerifyFailCustomRadioButton" Tag="VerifyFail" GroupName="verifyFailSelect" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxCustomRadioButton_Checked">Customized</RadioButton>
+                </StackPanel>
+                <StackPanel x:Name="uxVerifyFailCustomStackPanel" Orientation="Horizontal" Margin="22,0,450,12" 
+                            Visibility="{Binding IsChecked,ElementName=uxVerifyFailCustomRadioButton,Converter={StaticResource b2v}}">
+                    <Button Tag="VerifyFail" Content="Browse" HorizontalAlignment="Left" Width="90" Height="35" 
+                            Style="{StaticResource ButtonRevealStyle}" Click="uxBrowseButton_Click"/>
+                    <TextBox x:Name="uxVerifyFailExploreTextBox" VerticalContentAlignment="Center" Margin="1,0,0,0" Width="270" Height="35" 
+                             Style="{StaticResource TextBoxRevealStyle}" Text="Please select an image file" IsReadOnly="True"/>
+                </StackPanel>
+                <StackPanel Margin="22,0,450,10" Height="15"/>
+            </StackPanel>
+        </ScrollViewer>
+    </Grid>
+</UserControl>

+ 191 - 0
AuthenticationUC.xaml.cs

@@ -0,0 +1,191 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using Microsoft.WindowsAPICodePack.Dialogs;
+
+namespace Phihong_EVSE_UI_Tool
+{
+    /// <summary>
+    /// AuthenticationUC.xaml 的互動邏輯
+    /// </summary>
+    public partial class AuthenticationUC : UserControl
+    {
+        private Properties.Settings mySettings = Properties.Settings.Default;
+        private string verifyCustomImgPath;
+        private string verifyOkCustomImgPath;
+        private string verifyFailCustomImgPath;
+
+        public AuthenticationUC()
+        {
+            InitializeComponent();
+
+            verifyCustomImgPath = mySettings.VerifyCustomImgPath;
+            uxVerifyDefaultRadioButton.IsChecked = mySettings.IsVerifyDefault;
+            uxVerifyCustomRadioButton.IsChecked = !mySettings.IsVerifyDefault;
+            uxVerifyExploreTextBox.Text = mySettings.VerifyCustomImgPath_org;
+
+            verifyOkCustomImgPath = mySettings.VerifyOkCustomImgPath;
+            uxVerifyOkDefaultRadioButton.IsChecked = mySettings.IsVerifyOkDefault;
+            uxVerifyOkCustomRadioButton.IsChecked = !mySettings.IsVerifyOkDefault;
+            uxVerifyOkExploreTextBox.Text = mySettings.VerifyOkCustomImgPath_org;
+
+            verifyFailCustomImgPath = mySettings.VerifyFailCustomImgPath;
+            uxVerifyFailDefaultRadioButton.IsChecked = mySettings.IsVerifyFailDefault;
+            uxVerifyFailCustomRadioButton.IsChecked = !mySettings.IsVerifyFailDefault;
+            uxVerifyFailExploreTextBox.Text = mySettings.VerifyFailCustomImgPath_org;
+        }
+
+        private void uxDefaultRadioButton_Checked(object sender, RoutedEventArgs e)
+        {
+            Uri srcPath;
+            RadioButton rb = e.Source as RadioButton;
+            if (rb is null)
+            {
+                return;
+            }
+
+            switch (rb.Tag.ToString())
+            {
+                case "Verify":
+                    srcPath = new Uri(Utility.BG_PARENTFOLDER + Utility.BG_VERIFY, UriKind.Relative);
+                    uxVerifyImage.Source = new BitmapImage(srcPath);
+                    Utility.CopyFileFromResource(srcPath, Utility.OUTPUT_DIRECTORY + Utility.BG_VERIFY);
+                    mySettings.IsVerifyDefault = true;
+                    break;
+                case "VerifyOk":
+                    srcPath = new Uri(Utility.BG_PARENTFOLDER + Utility.BG_VERIFYOK, UriKind.Relative);
+                    uxVerifyOkImage.Source = new BitmapImage(srcPath);
+                    Utility.CopyFileFromResource(srcPath, Utility.OUTPUT_DIRECTORY + Utility.BG_VERIFYOK);
+                    mySettings.IsVerifyOkDefault = true;
+                    break;
+                case "VerifyFail":
+                    srcPath = new Uri(Utility.BG_PARENTFOLDER + Utility.BG_VERIFYFAIL, UriKind.Relative);
+                    uxVerifyFailImage.Source = new BitmapImage(srcPath);
+                    Utility.CopyFileFromResource(srcPath, Utility.OUTPUT_DIRECTORY + Utility.BG_VERIFYFAIL);
+                    mySettings.IsVerifyFailDefault = true;
+                    break;
+                default:
+                    return;
+            }
+
+            mySettings.Save();
+        }
+
+        private void uxCustomRadioButton_Checked(object sender, RoutedEventArgs e)
+        {
+            RadioButton rb = e.Source as RadioButton;
+            if (rb is null)
+            {
+                return;
+            }
+
+            switch (rb.Tag.ToString())
+            {
+                case "Verify":
+                    if (File.Exists(verifyCustomImgPath))
+                    {
+                        Utility.LoadImageFromPath(uxVerifyImage, verifyCustomImgPath);
+                        File.Copy(verifyCustomImgPath, Utility.OUTPUT_DIRECTORY + Utility.BG_VERIFY, true);
+                        mySettings.IsVerifyDefault = false;
+                    }
+                    break;
+                case "VerifyOk":
+                    if (File.Exists(verifyOkCustomImgPath))
+                    {
+                        Utility.LoadImageFromPath(uxVerifyOkImage, verifyOkCustomImgPath);
+                        File.Copy(verifyOkCustomImgPath, Utility.OUTPUT_DIRECTORY + Utility.BG_VERIFYOK, true);
+                        mySettings.IsVerifyOkDefault = false;
+                    }
+                    break;
+                case "VerifyFail":
+                    if (File.Exists(verifyFailCustomImgPath))
+                    {
+                        Utility.LoadImageFromPath(uxVerifyFailImage, verifyFailCustomImgPath);
+                        File.Copy(verifyFailCustomImgPath, Utility.OUTPUT_DIRECTORY + Utility.BG_VERIFYFAIL, true);
+                        mySettings.IsVerifyFailDefault = false;
+                    }
+                    break;
+                default:
+                    return;
+            }
+
+            mySettings.Save();
+        }
+
+        private void uxBrowseButton_Click(object sender, RoutedEventArgs e)
+        {
+            Button btn = e.Source as Button;
+            if (btn is null)
+            {
+                return;
+            }
+
+            var dlg = new CommonOpenFileDialog()
+            {
+                EnsureFileExists = true,
+                Title = "Select an image file",
+            };
+            dlg.Filters.Add(new CommonFileDialogFilter("BMP images", "*.bmp"));
+
+            if (dlg.ShowDialog() == CommonFileDialogResult.Ok)
+            {
+                if (!Utility.IsBackgroundImageSizeQualified(dlg.FileName))
+                {
+                    MessageBox.Show(dlg.FileName + "\r\n" + "The resolution of the image must be 800x480",
+                                    "Incorrect Resolution", MessageBoxButton.OK, MessageBoxImage.Error);
+                    return;
+                }
+
+                switch (btn.Tag.ToString())
+                {
+                    case "Verify":
+                        uxVerifyExploreTextBox.Text = dlg.FileName;
+                        verifyCustomImgPath = Utility.CUSTOM_DIRECTORY + Utility.BG_VERIFY;
+                        File.Copy(dlg.FileName, verifyCustomImgPath, true);
+                        File.Copy(dlg.FileName, Utility.OUTPUT_DIRECTORY + Utility.BG_VERIFY, true);
+                        Utility.LoadImageFromPath(uxVerifyImage, verifyCustomImgPath);
+                        mySettings.IsVerifyDefault = false;
+                        mySettings.VerifyCustomImgPath = verifyCustomImgPath;
+                        mySettings.VerifyCustomImgPath_org = uxVerifyExploreTextBox.Text;
+                        break;
+                    case "VerifyOk":
+                        uxVerifyOkExploreTextBox.Text = dlg.FileName;
+                        verifyOkCustomImgPath = Utility.CUSTOM_DIRECTORY + Utility.BG_VERIFYOK;
+                        File.Copy(dlg.FileName, verifyOkCustomImgPath, true);
+                        File.Copy(dlg.FileName, Utility.OUTPUT_DIRECTORY + Utility.BG_VERIFYOK, true);
+                        Utility.LoadImageFromPath(uxVerifyOkImage, verifyOkCustomImgPath);
+                        mySettings.IsVerifyOkDefault = false;
+                        mySettings.VerifyOkCustomImgPath = verifyOkCustomImgPath;
+                        mySettings.VerifyOkCustomImgPath_org = uxVerifyOkExploreTextBox.Text;
+                        break;
+                    case "VerifyFail":
+                        uxVerifyFailExploreTextBox.Text = dlg.FileName;
+                        verifyFailCustomImgPath = Utility.CUSTOM_DIRECTORY + Utility.BG_VERIFYFAIL;
+                        File.Copy(dlg.FileName, verifyFailCustomImgPath, true);
+                        File.Copy(dlg.FileName, Utility.OUTPUT_DIRECTORY + Utility.BG_VERIFYFAIL, true);
+                        Utility.LoadImageFromPath(uxVerifyFailImage, verifyFailCustomImgPath);
+                        mySettings.IsVerifyFailDefault = false;
+                        mySettings.VerifyFailCustomImgPath = verifyFailCustomImgPath;
+                        mySettings.VerifyFailCustomImgPath_org = uxVerifyFailExploreTextBox.Text;
+                        break;
+                    default:
+                        break;
+                }
+            }
+
+            mySettings.Save();
+        }
+    }
+}

BIN
Background/0_init.bmp


BIN
Background/1_idle.bmp


BIN
Background/2_verify.bmp


BIN
Background/3_carChkok.bmp


BIN
Background/4_chkfail.bmp


BIN
Background/5_plug.bmp


BIN
Background/6_precharge.bmp


BIN
Background/7_charging.bmp


BIN
Background/8_complete.bmp


BIN
Background/9_fix.bmp


+ 49 - 0
BuildIco.xaml

@@ -0,0 +1,49 @@
+<UserControl x:Class="Phihong_EVSE_UI_Tool.BuildIco"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Phihong_EVSE_UI_Tool"
+             xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
+             xmlns:fd="clr-namespace:SourceChord.FluentWPF;assembly=FluentWPF"
+             mc:Ignorable="d" 
+             d:DesignHeight="500" d:DesignWidth="880" FontSize="15" FontFamily="Segoe UI">
+    <UserControl.Resources>
+        <BooleanToVisibilityConverter x:Key="b2v"/>
+    </UserControl.Resources>
+    <Grid fd:PointerTracker.Enabled="True">
+        <ScrollViewer x:Name="uxContentScrollViewer" HorizontalContentAlignment="Center" PanningMode="VerticalOnly" IsDeferredScrollingEnabled="False" DataContext="{Binding}">
+            <StackPanel>
+                <TextBlock Text="New" FontSize="24" HorizontalAlignment="Left" Margin="22,6,0,8"/>
+                <TextBlock Text="Load images to build ICO" HorizontalAlignment="Left" Margin="22,6,0,12"/>
+                <StackPanel Orientation="Horizontal" Margin="22,0,380,12">
+                    <Button Tag="Load" Content="Browse" HorizontalAlignment="Left" Width="90" Height="35" 
+                            Style="{StaticResource ButtonRevealStyle}" Click="uxBrowseButton_Click"/>
+                    <TextBox x:Name="uxImageBrowseTextBox" VerticalContentAlignment="Center" Margin="1,0,0,0" Width="270" Height="35" 
+                             Style="{StaticResource TextBoxRevealStyle}" IsReadOnly="True"/>
+                    <Button x:Name="uxRefreshButton" Margin="3,0,0,0" Width="35" 
+                            Style="{StaticResource ButtonRevealStyle}" Click="uxRefreshButton_Click">
+                        <md:PackIcon Kind="Refresh" VerticalAlignment="Center"/>
+                    </Button>
+                </StackPanel>
+                <StackPanel x:Name="uxDataStackPanel" Margin="22,0,180,12" Visibility="Collapsed">
+                    <DataGrid x:Name="uxImagesDataGrid"  AutoGenerateColumns="False" IsReadOnly="True" 
+                              Height="245" Width="470" HorizontalAlignment="Left">
+                        <DataGrid.Columns>
+                            <DataGridTextColumn Header="Index" Binding="{Binding Index}"/>
+                            <DataGridTextColumn Header="FileName" Binding="{Binding FileName}" Width="220"/>
+                            <DataGridTextColumn Header="Width" Binding="{Binding PixelWidth}"/>
+                            <DataGridTextColumn Header="Height" Binding="{Binding PixelHeight}"/>
+                        </DataGrid.Columns>
+                    </DataGrid>
+                    <Button x:Name="uxBuildButton" Tag="Build" Content="Build" HorizontalAlignment="Left" Width="90" Height="35" Margin="0,18,0,12"  
+                            Style="{StaticResource ButtonRevealStyle}" Click="uxBuildButton_Click" Visibility="Collapsed"/>
+                    <Viewbox x:Name="viewbox" Stretch="Uniform">
+                        <UniformGrid x:Name="uxIcoUniformGrid" Background="AliceBlue"/>
+                    </Viewbox>
+                </StackPanel>
+                <StackPanel Margin="22,0,450,10" Height="15"/>
+            </StackPanel>
+        </ScrollViewer>
+    </Grid>
+</UserControl>

+ 223 - 0
BuildIco.xaml.cs

@@ -0,0 +1,223 @@
+using Microsoft.WindowsAPICodePack.Dialogs;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace Phihong_EVSE_UI_Tool
+{
+    /// <summary>
+    /// BuildIco.xaml 的互動邏輯
+    /// </summary>
+    public partial class BuildIco : UserControl
+    {
+        private string imgFolderPath;
+        private string icoFileName;
+        private List<ImageList> imageList;
+        private int bytesPerPixel = 2;
+
+        public BuildIco()
+        {
+            InitializeComponent();
+
+            imageList = new List<ImageList>();
+        }
+
+        private void GenerateImageList()
+        {
+            imageList.Clear();
+            uxDataStackPanel.Visibility = Visibility.Visible;
+            uxBuildButton.Visibility = Visibility.Collapsed;
+            uxIcoUniformGrid.Visibility = Visibility.Collapsed;
+            uxImagesDataGrid.ItemsSource = null;
+            uxImagesDataGrid.Items.Refresh();
+
+            Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, new Action(() => { }));
+
+            DirectoryInfo directoryInfo = new DirectoryInfo(imgFolderPath);
+            var files = directoryInfo.GetFiles().Where(s => s.Extension.ToLower() == ".bmp" ||
+                                                            s.Extension.ToLower() == ".jpg" ||
+                                                            s.Extension.ToLower() == ".png");
+
+            foreach (FileInfo f in files)
+            {
+                int idx = f.Name.IndexOf(f.Name.FirstOrDefault<char>(e => !char.IsNumber(e)));
+                if (idx != -1)
+                {
+                    if (!Utility.IsIcoImageSizeQualified(f.FullName))
+                    {
+                        MessageBox.Show("Warning! " + f.Name + "\r\n" +
+                                        "The width and height of the image must be <= 255");
+                        continue;
+                    }
+
+                    int index = Int32.Parse(f.Name.Substring(0, idx));
+
+                    int width = 0, height = 0;
+                    byte[] pixels = null;
+                    Utility.ConvertToBitmap565Array(f.FullName, out width, out height, out pixels);
+
+                    imageList.Add(new ImageList(index, System.IO.Path.GetFileName(f.FullName),
+                                                (byte)width, (byte)height, pixels));
+
+                }
+            }
+
+            imageList = imageList.OrderBy(e => e.Index).ToList();
+            uxImagesDataGrid.ItemsSource = imageList;
+            uxBuildButton.Visibility = Visibility.Visible;
+            uxIcoUniformGrid.Visibility = Visibility.Visible;
+        }
+
+        private void BuildIcoFile()
+        {
+            icoFileName = Utility.OUTPUT_DIRECTORY + Utility.ICO_NAME;
+            FileStream icoFile = new FileStream(icoFileName, FileMode.Create, FileAccess.ReadWrite);
+            byte[] array = new byte[4 * bytesPerPixel];
+            long contentOffset = 131072 * bytesPerPixel;
+
+            foreach (ImageList i in imageList)
+            {
+                array[0] = i.PixelWidth;
+                array[1] = i.PixelHeight;
+                array[2] = (byte)(contentOffset / bytesPerPixel >> 24);
+                array[3] = (byte)(contentOffset / bytesPerPixel >> 16);
+                array[4] = (byte)(contentOffset / bytesPerPixel >> 8);
+                array[5] = (byte)(contentOffset / bytesPerPixel);
+                array[6] = i.Pixels[0];
+                array[7] = i.Pixels[1];
+
+                //Header
+                icoFile.Position = i.Index * 4 * bytesPerPixel; //header offset
+                icoFile.Write(array, 0, 4 * bytesPerPixel);
+
+                //Content
+                icoFile.Position = contentOffset;
+                icoFile.Write(i.Pixels, 0, i.PixelWidth * i.PixelHeight * bytesPerPixel);
+                contentOffset += i.PixelWidth * i.PixelHeight * bytesPerPixel;
+            }
+
+            icoFile.Close();
+        }
+
+        private void DisplayDWIcoContent()
+        {
+            FileStream fs = new FileStream(icoFileName, FileMode.OpenOrCreate, FileAccess.ReadWrite);
+
+            if (fs.Length > 131072 * bytesPerPixel)
+            {
+                for (int i = 0; i <= 32767; i++)
+                {
+                    byte[] array = new byte[4 * bytesPerPixel];
+                    fs.Position = i * 4 * bytesPerPixel;
+                    fs.Read(array, 0, 4 * bytesPerPixel);
+                    int width = array[0];
+                    int height = array[1];
+                    long offset = ((array[2] << 24) + (array[3] << 16) + (array[4] << 8) + array[5]) * bytesPerPixel;
+                    if (offset <= 0)
+                    {
+                        continue;
+                    }
+                    if (offset >= 131072 * bytesPerPixel)
+                    {
+                        DisPlayIco(fs, width, height, offset);
+
+                        Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, new Action(() => { }));
+                    }
+                }
+            }
+
+            fs.Close();
+        }
+
+        public void DisPlayIco(FileStream fs, int width, int height, long offset)
+        {
+            WriteableBitmap wbitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgr565, null);
+            byte[,,] pixels = new byte[width, height, 2];
+            byte[] pixels1d = new byte[height * width * bytesPerPixel];
+
+            fs.Position = offset;
+            byte[] array = new byte[bytesPerPixel];
+            for (int i = 0; i < height; i++)
+            {
+                for (int j = 0; j < width; j++)
+                {
+                    fs.Read(array, 0, bytesPerPixel);
+                    pixels[j, i, 0] = array[1];
+                    pixels[j, i, 1] = array[0];
+                }
+            }
+
+            int index = 0;
+            for (int row = 0; row < height; row++)
+            {
+                for (int col = 0; col < width; col++)
+                {
+                    for (int i = 0; i < bytesPerPixel; i++)
+                    {
+                        pixels1d[index++] = pixels[col, row, i];
+                    }
+                }
+            }
+
+            Int32Rect rect = new Int32Rect(0, 0, width, height);
+            int stride = (width * wbitmap.Format.BitsPerPixel + 7) / 8;
+            wbitmap.WritePixels(rect, pixels1d, stride, 0);
+
+            Image image = new Image()
+            {
+                Width = 200,
+                Height = 200,
+                Stretch = Stretch.None,
+                Margin = new Thickness(0),
+                Source = wbitmap
+            };
+            RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality);
+
+            uxIcoUniformGrid.Children.Add(image);
+        }
+
+        private void uxBrowseButton_Click(object sender, RoutedEventArgs e)
+        {
+            var dlg = new CommonOpenFileDialog()
+            {
+                IsFolderPicker = true,
+                Title = "Select image files path",
+            };
+
+            if (dlg.ShowDialog() == CommonFileDialogResult.Ok)
+            {
+                imgFolderPath = dlg.FileName;
+                uxImageBrowseTextBox.Text = imgFolderPath;
+                GenerateImageList();
+            }
+        }
+
+        private void uxRefreshButton_Click(object sender, RoutedEventArgs e)
+        {
+            if (Directory.Exists(imgFolderPath))
+            {
+                GenerateImageList();
+            }
+        }
+
+        private void uxBuildButton_Click(object sender, RoutedEventArgs e)
+        {
+            uxIcoUniformGrid.Children.Clear();
+            BuildIcoFile();
+            DisplayDWIcoContent();
+        }
+    }
+}

+ 65 - 0
Enums.cs

@@ -0,0 +1,65 @@
+namespace Phihong_EVSE_UI_Tool
+{
+    public enum IcoElement
+    {
+        AdminOff = 1,
+        AdminOn,
+        Alert,
+        ArrowDark,
+        ArrowLight, //5
+        SignalOff,
+        SignalOn,
+        WifiOff,
+        WifiOn,
+        Logo, //10
+        ConnectionDark,
+        ConnectionLight,
+        ButtonGun,
+        ButtonHome,
+        StopCharging, //15
+        StopChargingRfid,
+        ChademoDark,
+        CssDark,
+        GbtDark,
+        AcDark, //20
+        ChademoLight,
+        CssLight,
+        GbtLight,
+        AcLight,
+        MainRfidOff, //25
+        MainRfidOn,
+        MainAppOff,
+        MainAppOn,
+        MainQrOff,
+        MainQrOn, //30
+        ChargingDark,
+        ChargingLight,
+        BatteryEmpty,
+        Battery20,
+        Battery40, //35
+        Battery60,
+        Battery80,
+        Battery100,
+        Charge,
+        Flash, //40
+        HourGlass,
+        ChargingComplete,
+        Battery20Complete,
+        Battery40Complete,
+        Battery60Complete, //45
+        Battery80Complete,
+        Battery100Complete,
+        ChargeComplete,
+        CostComplete,
+        TimeComplete, //50
+        IconUnit,
+        SideRfidOff,
+        SideRfidOn,
+        SideAppOff,
+        SideAppOn, //55
+        SideQrOff,
+        SideQrOn,
+        EthernetOff,
+        EthernetOn, //59
+    }
+}

BIN
ICO/10_logo.bmp


BIN
ICO/60.ICO


+ 56 - 0
ImageList.cs

@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Media;
+
+namespace Phihong_EVSE_UI_Tool
+{
+    public class ImageList
+    {
+        public int Index
+        {
+            get;
+            set;
+        }
+
+        public string FileName
+        {
+            get;
+            set;
+        }
+
+        public byte PixelWidth
+        {
+            get;
+            set;
+        }
+
+        public byte PixelHeight
+        {
+            get;
+            set;
+        }
+
+        public byte[] Pixels
+        {
+            get;
+            set;
+        }
+
+        public ImageList()
+        {
+            ;
+        }
+
+        public ImageList(int index, string fileName, byte pixelWidth, byte pixelHeight, byte[] pixels)
+        {
+            this.Index = index;
+            this.FileName = fileName;
+            this.PixelWidth = pixelWidth;
+            this.PixelHeight = pixelHeight;
+            this.Pixels = pixels;
+        }
+    }
+}

+ 58 - 0
InitialIdleUC.xaml

@@ -0,0 +1,58 @@
+<UserControl x:Class="Phihong_EVSE_UI_Tool.InitialIdleUC"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Phihong_EVSE_UI_Tool"
+             xmlns:fd="clr-namespace:SourceChord.FluentWPF;assembly=FluentWPF"
+             mc:Ignorable="d" 
+             d:DesignHeight="750" d:DesignWidth="880" FontSize="15" FontFamily="Segoe UI">
+    <UserControl.Resources>
+        <BooleanToVisibilityConverter x:Key="b2v"/>
+    </UserControl.Resources>
+    <Grid fd:PointerTracker.Enabled="True">
+        <ScrollViewer x:Name="uxContentScrollViewer" HorizontalContentAlignment="Center" PanningMode="VerticalOnly" IsDeferredScrollingEnabled="False" DataContext="{Binding}">
+            <StackPanel>
+                <!-- Initial -->
+                <TextBlock Text="Initial" FontSize="24" HorizontalAlignment="Left" Margin="22,6,0,8"/>
+                <TextBlock Text="Background picture for the Initial status (800x480)" HorizontalAlignment="Left" Margin="22,4,0,12"/>
+                <Image x:Name="uxInitImage" Source="/Background/0_init.bmp" HorizontalAlignment="Left" Margin="22,0,0,12" Height="192" Width="320" RenderOptions.BitmapScalingMode="HighQuality"/>
+                <StackPanel Orientation="Horizontal" Margin="22,8,370,12">
+                    <RadioButton x:Name="uxInitDefaultRadioButton" Tag="Initial" GroupName="initSelect" Margin="0,0,36,0" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxDefaultRadioButton_Checked">Default</RadioButton>
+                    <RadioButton x:Name="uxInitCustomRadioButton" Tag="Initial" GroupName="initSelect" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxCustomRadioButton_Checked">Customized</RadioButton>
+                </StackPanel>
+                <StackPanel x:Name="uxInitCustomStackPanel" Orientation="Horizontal" Margin="22,0,370,12" 
+                            Visibility="{Binding IsChecked,ElementName=uxInitCustomRadioButton,Converter={StaticResource b2v}}">
+                    <Button Tag="Initial" Content="Browse" HorizontalAlignment="Left" Width="90" Height="35" 
+                            Style="{StaticResource ButtonRevealStyle}" Click="uxExploreButton_Click" />
+                    <TextBox x:Name="uxInitExploreTextBox" VerticalContentAlignment="Center" Margin="1,0,0,0" Width="270" Height="35" 
+                             Style="{StaticResource TextBoxRevealStyle}" Text="Please select an image file"/>
+                </StackPanel>
+                <!-- Idle -->
+                <TextBlock Text="Idle" FontSize="24" HorizontalAlignment="Left" Margin="22,18,0,8"/>
+                <TextBlock Text="Background picture for the Idle status (800x480)" HorizontalAlignment="Left" Margin="22,4,0,12"/>
+                <Image x:Name="uxIdleImage" Source="/Background/1_idle.bmp" HorizontalAlignment="Left" Margin="22,0,0,12" Height="192" Width="320" RenderOptions.BitmapScalingMode="HighQuality"/>
+                <StackPanel Orientation="Horizontal" Margin="22,8,370,12">
+                    <RadioButton x:Name="uxIdleDefaultRadioButton" Tag="Idle" GroupName="idleSelect" Margin="0,0,36,0" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxDefaultRadioButton_Checked">Default</RadioButton>
+                    <RadioButton x:Name="uxIdleCustomRadioButton" Tag="Idle" GroupName="idleSelect" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxCustomRadioButton_Checked">Customized</RadioButton>
+                </StackPanel>
+                <StackPanel x:Name="uxIdleCustomStackPanel" Orientation="Horizontal" Margin="22,0,370,12" 
+                            Visibility="{Binding IsChecked,ElementName=uxIdleCustomRadioButton,Converter={StaticResource b2v}}">
+                    <Button Tag="Idle" Content="Browse" HorizontalAlignment="Left" Width="90" Height="35" 
+                            Style="{StaticResource ButtonRevealStyle}" Click="uxExploreButton_Click"/>
+                    <TextBox x:Name="uxIdleExploreTextBox" VerticalContentAlignment="Center" Margin="1,0,0,0" Width="270" Height="35" 
+                             Style="{StaticResource TextBoxRevealStyle}" Text="Please select an image file"/>
+                </StackPanel>
+                <StackPanel Margin="22,0,370,10" Height="15"/>
+            </StackPanel>
+        </ScrollViewer>
+    </Grid>
+</UserControl>

+ 162 - 0
InitialIdleUC.xaml.cs

@@ -0,0 +1,162 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using Microsoft.WindowsAPICodePack.Dialogs;
+
+namespace Phihong_EVSE_UI_Tool
+{
+    /// <summary>
+    /// InitialIdleUC.xaml 的互動邏輯
+    /// </summary>
+    public partial class InitialIdleUC : UserControl
+    {
+        private Properties.Settings mySettings = Properties.Settings.Default;
+        private string initCustomImgPath;
+        private string idleCustomImgPath;
+
+        public InitialIdleUC()
+        {
+            InitializeComponent();
+
+            initCustomImgPath = mySettings.InitialCustomImgPath;
+            uxInitDefaultRadioButton.IsChecked = mySettings.IsInitialDefault;
+            uxInitCustomRadioButton.IsChecked = !mySettings.IsInitialDefault;
+            uxInitExploreTextBox.Text = mySettings.InitialCustomImgPath_org;
+
+            idleCustomImgPath = mySettings.IdleCustomImgPath;
+            uxIdleDefaultRadioButton.IsChecked = mySettings.IsIdleDefault;
+            uxIdleCustomRadioButton.IsChecked = !mySettings.IsIdleDefault;
+            uxIdleExploreTextBox.Text = mySettings.IdleCustomImgPath_org;
+        }
+
+        private void uxDefaultRadioButton_Checked(object sender, RoutedEventArgs e)
+        {
+            Uri srcPath;
+            RadioButton rb = e.Source as RadioButton;
+            if (rb is null)
+            {
+                return;
+            }
+
+            switch (rb.Tag.ToString())
+            {
+                case "Initial":
+                    srcPath = new Uri(Utility.BG_PARENTFOLDER + Utility.BG_INITIAL, UriKind.Relative);
+                    uxInitImage.Source = new BitmapImage(srcPath);
+                    Utility.CopyFileFromResource(srcPath, Utility.OUTPUT_DIRECTORY + Utility.BG_INITIAL);
+                    mySettings.IsInitialDefault = true;
+                    break;
+                case "Idle":
+                    srcPath = new Uri(Utility.BG_PARENTFOLDER + Utility.BG_IDLE, UriKind.Relative);
+                    uxIdleImage.Source = new BitmapImage(srcPath);
+                    Utility.CopyFileFromResource(srcPath, Utility.OUTPUT_DIRECTORY + Utility.BG_IDLE);
+                    mySettings.IsIdleDefault = true;
+                    break;
+                default:
+                    return;
+            }
+
+            mySettings.Save();
+        }
+
+        private void uxCustomRadioButton_Checked(object sender, RoutedEventArgs e)
+        {
+            RadioButton rb = e.Source as RadioButton;
+            if (rb is null)
+            {
+                return;
+            }
+
+            switch (rb.Tag.ToString())
+            {
+                case "Initial":
+                    if (File.Exists(initCustomImgPath))
+                    {
+                        Utility.LoadImageFromPath(uxInitImage, initCustomImgPath);
+                        File.Copy(initCustomImgPath, Utility.OUTPUT_DIRECTORY + Utility.BG_INITIAL, true);
+                        mySettings.IsInitialDefault = false;
+                    }
+                    break;
+                case "Idle":
+                    if (File.Exists(idleCustomImgPath))
+                    {
+                        Utility.LoadImageFromPath(uxIdleImage, idleCustomImgPath);
+                        File.Copy(initCustomImgPath, Utility.OUTPUT_DIRECTORY + Utility.BG_IDLE, true);
+                        mySettings.IsIdleDefault = false;
+                    }
+                    break;
+                default:
+                    return;
+            }
+
+            mySettings.Save();
+        }
+
+        private void uxExploreButton_Click(object sender, RoutedEventArgs e)
+        {
+            Button btn = e.Source as Button;
+            if (btn is null)
+            {
+                return;
+            }
+
+            var dlg = new CommonOpenFileDialog()
+            {
+                EnsureFileExists = true,
+                Title = "Select an image file",
+            };
+            dlg.Filters.Add(new CommonFileDialogFilter("BMP images", "*.bmp"));
+
+            if (dlg.ShowDialog() == CommonFileDialogResult.Ok)
+            {
+                if (!Utility.IsBackgroundImageSizeQualified(dlg.FileName))
+                {
+                    MessageBox.Show(dlg.FileName + "\r\n" + "The resolution of the image must be 800x480",
+                                    "Incorrect Resolution", MessageBoxButton.OK, MessageBoxImage.Error);
+                    return;
+                }
+
+                switch (btn.Tag.ToString())
+                {
+                    case "Initial":
+                        uxInitExploreTextBox.Text = dlg.FileName;
+                        initCustomImgPath = Utility.CUSTOM_DIRECTORY + Utility.BG_INITIAL;
+                        File.Copy(dlg.FileName, initCustomImgPath, true);
+                        File.Copy(dlg.FileName, Utility.OUTPUT_DIRECTORY + Utility.BG_INITIAL, true);
+                        Utility.LoadImageFromPath(uxInitImage, initCustomImgPath);
+                        mySettings.IsInitialDefault = false;
+                        mySettings.InitialCustomImgPath = initCustomImgPath;
+                        mySettings.InitialCustomImgPath_org = uxInitExploreTextBox.Text;
+                        break;
+                    case "Idle":
+                        uxIdleExploreTextBox.Text = dlg.FileName;
+                        idleCustomImgPath = Utility.CUSTOM_DIRECTORY + Utility.BG_IDLE;
+                        File.Copy(dlg.FileName, idleCustomImgPath, true);
+                        File.Copy(dlg.FileName, Utility.OUTPUT_DIRECTORY + Utility.BG_IDLE, true);
+                        Utility.LoadImageFromPath(uxIdleImage, idleCustomImgPath);
+                        mySettings.IsIdleDefault = false;
+                        mySettings.IdleCustomImgPath = idleCustomImgPath;
+                        mySettings.IdleCustomImgPath_org = uxIdleExploreTextBox.Text;
+                        break;
+                    default:
+                        break;
+                }
+            }
+
+            mySettings.Save();
+        }
+    }
+}

+ 87 - 0
MainWindow.xaml

@@ -0,0 +1,87 @@
+<Window x:Class="Phihong_EVSE_UI_Tool.MainWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:local="clr-namespace:Phihong_EVSE_UI_Tool"
+        mc:Ignorable="d"
+        xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
+        xmlns:fd="clr-namespace:SourceChord.FluentWPF;assembly=FluentWPF"
+        fd:AcrylicWindow.Enabled="True" fd:AcrylicWindow.AcrylicWindowStyle="NoIcon"
+        fd:AcrylicWindow.ExtendViewIntoTitleBar="True"
+        fd:AcrylicWindow.TintColor="#FFFFFF" fd:AcrylicWindow.NoiseOpacity="0.02"
+        Title="Phihong EVSE UI Tool" Height="800" Width="1200" FontFamily="Segoe UI"
+        WindowStyle="None" AllowsTransparency="True" WindowStartupLocation="CenterScreen">
+    <Grid>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="320"/>
+            <ColumnDefinition Width="*"/>
+        </Grid.ColumnDefinitions>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="100"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+
+        <Grid x:Name="uxMenuGrid" Grid.Column="0" Grid.RowSpan="2" fd:PointerTracker.Enabled="True" Background="#1FE6E6E6">
+            <StackPanel>
+                <TextBlock Text="Phihong EVSE UI Tool" HorizontalAlignment="Left" Margin="16,8,0,12"/>
+                <!-- Background -->
+                <TextBlock Text="Background" FontWeight="SemiBold" FontSize="15" HorizontalAlignment="Left" Margin="16,28,0,18"/>
+                <ListView x:Name="uxMenuListView" FontSize="15" SelectionChanged="uxMenuListView_SelectionChanged">
+                    <ListViewItem x:Name="uxInitListViewItem" Padding="1">
+                        <StackPanel Orientation="Horizontal">
+                            <md:PackIcon Kind="Adjust" Margin="16,12,6,12" VerticalAlignment="Center"/>
+                            <TextBlock Text="Initial &amp; Idle" FontSize="15" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="12"/>
+                        </StackPanel>
+                    </ListViewItem>
+                    <ListViewItem x:Name="uxAuthListViewItem" Padding="1">
+                        <StackPanel Orientation="Horizontal">
+                            <md:PackIcon Kind="CreditCardCheckOutline" Margin="16,12,6,12" VerticalAlignment="Center"/>
+                            <TextBlock Text="Authentication" FontSize="15" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="12"/>
+                        </StackPanel>
+                    </ListViewItem>
+                    <ListViewItem x:Name="uxChargingListViewItem" Padding="1">
+                        <StackPanel Orientation="Horizontal">
+                            <md:PackIcon Kind="BatteryChargingMedium" Margin="16,12,6,12" VerticalAlignment="Center"/>
+                            <TextBlock Text="Plug &amp; Charging" FontSize="15" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="12"/>
+                        </StackPanel>
+                    </ListViewItem>
+                    <ListViewItem x:Name="uxMaintainListViewItem" Padding="1">
+                        <StackPanel Orientation="Horizontal">
+                            <md:PackIcon Kind="WrenchOutline" Margin="16,12,6,12" VerticalAlignment="Center"/>
+                            <TextBlock Text="Maintenance" FontSize="15" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="12"/>
+                        </StackPanel>
+                    </ListViewItem>
+                    <!-- Icon -->
+                    <ListViewItem Padding="1" IsEnabled="False">
+                        <TextBlock Text="Icon" FontWeight="SemiBold" FontSize="15" Foreground="Transparent" HorizontalAlignment="Left" Margin="16,30,0,18"/>
+                    </ListViewItem>
+                    <ListViewItem x:Name="uxBuildListViewItem" Padding="1">
+                        <StackPanel Orientation="Horizontal">
+                            <md:PackIcon Kind="FileImageOutline" Margin="16,12,6,12" VerticalAlignment="Center"/>
+                            <TextBlock Text="Build ICO" FontSize="15" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="12"/>
+                        </StackPanel>
+                    </ListViewItem>
+                    <ListViewItem x:Name="uxModifyListViewItem" Padding="1">
+                        <StackPanel Orientation="Horizontal">
+                            <md:PackIcon Kind="FolderOpenOutline" Margin="16,12,6,12" VerticalAlignment="Center"/>
+                            <TextBlock Text="Modify ICO" FontSize="15" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="12"/>
+                        </StackPanel>
+                    </ListViewItem>
+                </ListView>
+            </StackPanel>
+            <TextBlock Text="Icon" FontWeight="SemiBold" FontSize="15" HorizontalAlignment="Left" Margin="17,318,0,362"/>
+        </Grid>
+
+        <Grid x:Name="uxTitleGrid" Grid.Column="1" Grid.Row="0" Background="White" fd:PointerTracker.Enabled="True">
+            <StackPanel>
+                <TextBlock x:Name="uxTitleTextBlock" Text="Initial &amp; Idle" FontSize="30" FontWeight="Bold" HorizontalAlignment="Left" Margin="22,50,0,18"/>
+            </StackPanel>
+            <Border BorderThickness="0,0,0,2" Margin="0,0,0,-1" BorderBrush="White"/>
+        </Grid>
+
+        <Grid x:Name="uxContentGrid" Grid.Column="1" Grid.Row="1" Background="White" ShowGridLines="False" fd:PointerTracker.Enabled="True">
+            <!--<local:InitialIdleUC></local:InitialIdleUC>-->
+        </Grid>
+    </Grid>
+</Window>

+ 98 - 0
MainWindow.xaml.cs

@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using SourceChord.FluentWPF;
+
+namespace Phihong_EVSE_UI_Tool
+{
+    /// <summary>
+    /// MainWindow.xaml 的互動邏輯
+    /// </summary>
+    public partial class MainWindow : Window
+    {
+        private ListViewItem lastSelectedItem = null;
+        private InitialIdleUC ucInitialIdle = new InitialIdleUC();
+        private AuthenticationUC ucAuthentaction = new AuthenticationUC();
+        private PlugChargingUC ucPlugCharging = new PlugChargingUC();
+        private MaintenanceUC ucMaintenance = new MaintenanceUC();
+        private BuildIco ucBuildIco = new BuildIco();
+        private ModifyIco ucModifyIco = new ModifyIco();
+
+        public MainWindow()
+        {
+            InitializeComponent();
+            this.MaxHeight = SystemParameters.WorkArea.Height + 12.5;
+            this.MinWidth = 1200;
+            this.MinHeight = 800;
+
+            uxInitListViewItem.IsSelected = true;
+            uxInitListViewItem.Focus();
+        }
+
+        private void uxMenuListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
+        {
+            if (uxMenuListView.SelectedItem != null)
+            {
+                if (lastSelectedItem != null)
+                {
+                    lastSelectedItem.Foreground = new SolidColorBrush(Colors.Black);
+                    lastSelectedItem.Background = null;
+                }
+
+                ListViewItem item = uxMenuListView.SelectedItem as ListViewItem;
+
+                Binding myBinding = new Binding("ImmersiveSystemAccentBrush");
+                myBinding.Source = new AccentColors();
+                item.SetBinding(ListViewItem.ForegroundProperty, myBinding);
+                item.Background = new SolidColorBrush(Color.FromArgb(45, 0, 0, 0));
+
+                uxTitleTextBlock.Text = (item.Content as StackPanel).Children.OfType<TextBlock>().First().Text;
+
+                uxContentGrid.Children.Clear();
+                switch (item.Name)
+                {
+                    case "uxInitListViewItem":
+                        uxContentGrid.Children.Add(ucInitialIdle);
+                        uxContentGrid.Children.OfType<InitialIdleUC>().First().uxContentScrollViewer.ScrollToTop();
+                        break;
+                    case "uxAuthListViewItem":
+                        uxContentGrid.Children.Add(ucAuthentaction);
+                        uxContentGrid.Children.OfType<AuthenticationUC>().First().uxContentScrollViewer.ScrollToTop();
+                        break;
+                    case "uxChargingListViewItem":
+                        uxContentGrid.Children.Add(ucPlugCharging);
+                        uxContentGrid.Children.OfType<PlugChargingUC>().First().uxContentScrollViewer.ScrollToTop();
+                        break;
+                    case "uxMaintainListViewItem":
+                        uxContentGrid.Children.Add(ucMaintenance);
+                        uxContentGrid.Children.OfType<MaintenanceUC>().First().uxContentScrollViewer.ScrollToTop();
+                        break;
+                    case "uxBuildListViewItem":
+                        uxContentGrid.Children.Add(ucBuildIco);
+                        uxContentGrid.Children.OfType<BuildIco>().First().uxContentScrollViewer.ScrollToTop();
+                        break;
+                    case "uxModifyListViewItem":
+                        uxContentGrid.Children.Add(ucModifyIco);
+                        uxContentGrid.Children.OfType<ModifyIco>().First().uxContentScrollViewer.ScrollToTop();
+                        break;
+                    default:
+                        break;
+                }
+
+                lastSelectedItem = item;
+            }
+        }
+    }
+}

+ 39 - 0
MaintenanceUC.xaml

@@ -0,0 +1,39 @@
+<UserControl x:Class="Phihong_EVSE_UI_Tool.MaintenanceUC"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:local="clr-namespace:Phihong_EVSE_UI_Tool"
+             xmlns:fd="clr-namespace:SourceChord.FluentWPF;assembly=FluentWPF"
+             mc:Ignorable="d" 
+             d:DesignHeight="400" d:DesignWidth="880" FontSize="15" FontFamily="Segoe UI">
+    <UserControl.Resources>
+        <BooleanToVisibilityConverter x:Key="b2v"/>
+    </UserControl.Resources>
+    <Grid fd:PointerTracker.Enabled="True">
+        <ScrollViewer x:Name="uxContentScrollViewer" HorizontalContentAlignment="Center" PanningMode="VerticalOnly" IsDeferredScrollingEnabled="False" DataContext="{Binding}">
+            <StackPanel>
+                <!-- Maintenance -->
+                <TextBlock Text="Fix" FontSize="24" HorizontalAlignment="Left" Margin="22,6,0,8"/>
+                <TextBlock Text="Background picture for the Maintenance status (800x480)" HorizontalAlignment="Left" Margin="22,4,0,12"/>
+                <Image x:Name="uxMaintainImage" Source="/Background/9_fix.bmp" HorizontalAlignment="Left" Margin="22,0,0,12" Height="192" Width="320" RenderOptions.BitmapScalingMode="HighQuality"/>
+                <StackPanel Orientation="Horizontal" Margin="22,8,370,12">
+                    <RadioButton x:Name="uxMaintainDefaultRadioButton" Tag="Maintain" GroupName="maintainSelect" Margin="0,0,36,0" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxDefaultRadioButton_Checked">Default</RadioButton>
+                    <RadioButton x:Name="uxMaintainCustomRadioButton" Tag="Maintain" GroupName="maintainSelect" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxCustomRadioButton_Checked">Customized</RadioButton>
+                </StackPanel>
+                <StackPanel x:Name="uxMaintainCustomStackPanel" Orientation="Horizontal" Margin="22,0,450,12" 
+                            Visibility="{Binding IsChecked,ElementName=uxMaintainCustomRadioButton,Converter={StaticResource b2v}}">
+                    <Button Tag="Maintain" Content="Browse" HorizontalAlignment="Left" Width="90" Height="35" 
+                            Style="{StaticResource ButtonRevealStyle}" Click="uxBrowseButton_Click"/>
+                    <TextBox x:Name="uxMaintainExploreTextBox" VerticalContentAlignment="Center" Margin="1,0,0,0" Width="270" Height="35" 
+                             Style="{StaticResource TextBoxRevealStyle}" Text="Please select an image file" IsReadOnly="True"/>
+                </StackPanel>
+                <StackPanel Margin="22,0,450,10" Height="15"/>
+            </StackPanel>
+        </ScrollViewer>
+    </Grid>
+</UserControl>

+ 131 - 0
MaintenanceUC.xaml.cs

@@ -0,0 +1,131 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using Microsoft.WindowsAPICodePack.Dialogs;
+
+namespace Phihong_EVSE_UI_Tool
+{
+    /// <summary>
+    /// Maintenance.xaml 的互動邏輯
+    /// </summary>
+    public partial class MaintenanceUC : UserControl
+    {
+        private Properties.Settings mySettings = Properties.Settings.Default;
+        private string maintainCustomImgPath;
+
+        public MaintenanceUC()
+        {
+            InitializeComponent();
+
+            maintainCustomImgPath = mySettings.MaintainCustomImgPath;
+            uxMaintainDefaultRadioButton.IsChecked = mySettings.IsMaintainDefault;
+            uxMaintainCustomRadioButton.IsChecked = !mySettings.IsMaintainDefault;
+            uxMaintainExploreTextBox.Text = mySettings.MaintainCustomImgPath_org;
+        }
+
+        private void uxDefaultRadioButton_Checked(object sender, RoutedEventArgs e)
+        {
+            Uri srcPath;
+            RadioButton rb = e.Source as RadioButton;
+            if (rb is null)
+            {
+                return;
+            }
+
+            switch (rb.Tag.ToString())
+            {
+                case "Maintain":
+                    srcPath = new Uri(Utility.BG_PARENTFOLDER + Utility.BG_MAINTAIN, UriKind.Relative);
+                    uxMaintainImage.Source = new BitmapImage(srcPath);
+                    Utility.CopyFileFromResource(srcPath, Utility.OUTPUT_DIRECTORY + Utility.BG_MAINTAIN);
+                    mySettings.IsMaintainDefault = true;
+                    break;
+                default:
+                    return;
+            }
+
+            mySettings.Save();
+        }
+
+        private void uxCustomRadioButton_Checked(object sender, RoutedEventArgs e)
+        {
+            RadioButton rb = e.Source as RadioButton;
+            if (rb is null)
+            {
+                return;
+            }
+
+            switch (rb.Tag.ToString())
+            {
+                case "Maintain":
+                    if (File.Exists(maintainCustomImgPath))
+                    {
+                        Utility.LoadImageFromPath(uxMaintainImage, maintainCustomImgPath);
+                        File.Copy(maintainCustomImgPath, Utility.OUTPUT_DIRECTORY + Utility.BG_MAINTAIN, true);
+                        mySettings.IsMaintainDefault = false;
+                    }
+                    break;
+                default:
+                    return;
+            }
+
+            mySettings.Save();
+        }
+
+        private void uxBrowseButton_Click(object sender, RoutedEventArgs e)
+        {
+            Button btn = e.Source as Button;
+            if (btn is null)
+            {
+                return;
+            }
+
+            var dlg = new CommonOpenFileDialog()
+            {
+                EnsureFileExists = true,
+                Title = "Select an image file",
+            };
+            dlg.Filters.Add(new CommonFileDialogFilter("BMP images", "*.bmp"));
+
+            if (dlg.ShowDialog() == CommonFileDialogResult.Ok)
+            {
+                if (!Utility.IsBackgroundImageSizeQualified(dlg.FileName))
+                {
+                    MessageBox.Show(dlg.FileName + "\r\n" + "The resolution of the image must be 800x480",
+                                    "Incorrect Resolution", MessageBoxButton.OK, MessageBoxImage.Error);
+                    return;
+                }
+
+                switch (btn.Tag.ToString())
+                {
+                    case "Maintain":
+                        uxMaintainExploreTextBox.Text = dlg.FileName;
+                        maintainCustomImgPath = Utility.CUSTOM_DIRECTORY + Utility.BG_MAINTAIN;
+                        File.Copy(dlg.FileName, maintainCustomImgPath, true);
+                        File.Copy(dlg.FileName, Utility.OUTPUT_DIRECTORY + Utility.BG_MAINTAIN, true);
+                        Utility.LoadImageFromPath(uxMaintainImage, maintainCustomImgPath);
+                        mySettings.IsMaintainDefault = false;
+                        mySettings.MaintainCustomImgPath = maintainCustomImgPath;
+                        mySettings.MaintainCustomImgPath_org = uxMaintainExploreTextBox.Text;
+                        break;
+                    default:
+                        break;
+                }
+            }
+
+            mySettings.Save();
+        }
+    }
+}

+ 44 - 0
ModifyIco.xaml

@@ -0,0 +1,44 @@
+<UserControl x:Class="Phihong_EVSE_UI_Tool.ModifyIco"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:Phihong_EVSE_UI_Tool"
+             xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
+             xmlns:fd="clr-namespace:SourceChord.FluentWPF;assembly=FluentWPF"
+             mc:Ignorable="d" 
+             d:DesignHeight="500" d:DesignWidth="880" FontSize="15" FontFamily="Segoe UI">
+    <UserControl.Resources>
+        <BooleanToVisibilityConverter x:Key="b2v"/>
+    </UserControl.Resources>
+    <Grid fd:PointerTracker.Enabled="True">
+        <ScrollViewer x:Name="uxContentScrollViewer" HorizontalContentAlignment="Center" PanningMode="VerticalOnly" IsDeferredScrollingEnabled="False" DataContext="{Binding}">
+            <StackPanel>
+                <!-- Logo -->
+                <TextBlock Text="Change Logo" FontSize="24" HorizontalAlignment="Left" Margin="22,6,0,8"/>
+                <TextBlock Text="Logo image (186x34)" HorizontalAlignment="Left" Margin="22,6,0,12"/>
+                <Image x:Name="uxLogoImage" Source="/ICO/10_logo.bmp" HorizontalAlignment="Left" Margin="22,0,0,12" Height="70" Width="300" RenderOptions.BitmapScalingMode="HighQuality"/>
+                <StackPanel Orientation="Horizontal" Margin="22,8,370,12">
+                    <RadioButton x:Name="uxLogoDefaultRadioButton" Tag="Logo" GroupName="logoSelect" Margin="0,0,36,0" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxDefaultRadioButton_Checked">Default</RadioButton>
+                    <RadioButton x:Name="uxLogoCustomRadioButton" Tag="Logo" GroupName="logoSelect" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxCustomRadioButton_Checked">Customized</RadioButton>
+                </StackPanel>
+                <StackPanel x:Name="uxLogoCustomStackPanel" Orientation="Horizontal" Margin="22,0,450,12" 
+                            Visibility="{Binding IsChecked,ElementName=uxLogoCustomRadioButton,Converter={StaticResource b2v}}">
+                    <Button Tag="Logo" Content="Browse" HorizontalAlignment="Left" Width="90" Height="35"
+                            Style="{StaticResource ButtonRevealStyle}" Click="uxBrowseButton_Click"/>
+                    <TextBox x:Name="uxLogoBrowseTextBox" VerticalContentAlignment="Center" Margin="1,0,0,0" Width="270" Height="35" 
+                             Style="{StaticResource TextBoxRevealStyle}" Text="Please select logo image" IsReadOnly="True"/>
+                </StackPanel>
+                <TextBlock Text="ICO Preview" FontSize="24" HorizontalAlignment="Left" Margin="22,18,0,0"/>
+                <Viewbox x:Name="viewbox" Stretch="Uniform" Margin="22,12,180,12">
+                    <UniformGrid x:Name="uxIcoUniformGrid" Background="AliceBlue"/>
+                </Viewbox>
+                <StackPanel Margin="22,0,450,10" Height="15"/>
+            </StackPanel>
+        </ScrollViewer>
+    </Grid>
+</UserControl>

+ 256 - 0
ModifyIco.xaml.cs

@@ -0,0 +1,256 @@
+using Microsoft.WindowsAPICodePack.Dialogs;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace Phihong_EVSE_UI_Tool
+{
+    /// <summary>
+    /// ModifyIco.xaml 的互動邏輯
+    /// </summary>
+    public partial class ModifyIco : UserControl
+    {
+        private Properties.Settings mySettings = Properties.Settings.Default;
+        private string logoCustomImgPath;
+        private int bytesPerPixel = 2;
+
+        public ModifyIco()
+        {
+            InitializeComponent();
+
+            logoCustomImgPath = mySettings.LogoCustomImgPath;
+            uxLogoDefaultRadioButton.IsChecked = mySettings.IsLogoDefault;
+            uxLogoCustomRadioButton.IsChecked = !mySettings.IsLogoDefault;
+            uxLogoBrowseTextBox.Text = mySettings.LogoCustomImgPath_org;
+        }
+
+        private void ModifyIcoContent(int index)
+        {
+            Uri srcPath = new Uri(Utility.ICO_PARENTFOLDER + Utility.ICO_NAME, UriKind.Relative);
+            Utility.CopyFileFromResource(srcPath, Utility.OUTPUT_DIRECTORY + Utility.ICO_NAME);
+
+            if (mySettings.IsLogoDefault)
+            {
+                return;
+            }
+
+            int width = 0, height = 0;
+            byte[] pixels = null;
+            Utility.ConvertToBitmap565Array(logoCustomImgPath, out width, out height, out pixels);
+
+            FileStream icoFile = new FileStream(Utility.OUTPUT_DIRECTORY + Utility.ICO_NAME, FileMode.OpenOrCreate, FileAccess.ReadWrite);
+
+            byte[] array = new byte[4 * bytesPerPixel];
+            icoFile.Position = index * 4 * bytesPerPixel;
+            icoFile.Read(array, 0, 4 * bytesPerPixel);
+            long offset = ((array[2] << 24) + (array[3] << 16) + (array[4] << 8) + array[5]) * bytesPerPixel;
+
+            array[6] = pixels[0];
+            array[7] = pixels[1];
+
+            //Modify Header
+            icoFile.Position = index * 4 * bytesPerPixel; //header offset
+            icoFile.Write(array, 0, 4 * bytesPerPixel);
+
+            //Modify Content
+            icoFile.Position = offset;
+            icoFile.Write(pixels, 0, width * height * bytesPerPixel);
+
+            icoFile.Close();
+        }
+
+        private void DisplayDWIcoContent()
+        {
+            FileStream fs = new FileStream(Utility.OUTPUT_DIRECTORY + Utility.ICO_NAME, FileMode.OpenOrCreate, FileAccess.ReadWrite);
+
+            if (fs.Length > 131072 * bytesPerPixel)
+            {
+                for (int i = 0; i <= 32767; i++)
+                {
+                    byte[] array = new byte[4 * bytesPerPixel];
+                    fs.Position = i * 4 * bytesPerPixel;
+                    fs.Read(array, 0, 4 * bytesPerPixel);
+                    int width = array[0];
+                    int height = array[1];
+                    long offset = ((array[2] << 24) + (array[3] << 16) + (array[4] << 8) + array[5]) * bytesPerPixel;
+                    if (offset <= 0)
+                    {
+                        continue;
+                    }
+                    if (offset >= 131072 * bytesPerPixel)
+                    {
+                        DisPlayIco(fs, width, height, offset);
+
+                        Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, new Action(() => { }));
+                    }
+                }
+            }
+
+            fs.Close();
+        }
+
+        public void DisPlayIco(FileStream fs, int width, int height, long offset)
+        {
+            WriteableBitmap wbitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Bgr565, null);
+            byte[,,] pixels = new byte[width, height, 2];
+            byte[] pixels1d = new byte[height * width * bytesPerPixel];
+
+            fs.Position = offset;
+            byte[] array = new byte[bytesPerPixel];
+            for (int i = 0; i < height; i++)
+            {
+                for (int j = 0; j < width; j++)
+                {
+                    fs.Read(array, 0, bytesPerPixel);
+                    pixels[j, i, 0] = array[1];
+                    pixels[j, i, 1] = array[0];
+                }
+            }
+
+            int index = 0;
+            for (int row = 0; row < height; row++)
+            {
+                for (int col = 0; col < width; col++)
+                {
+                    for (int i = 0; i < bytesPerPixel; i++)
+                    {
+                        pixels1d[index++] = pixels[col, row, i];
+                    }
+                }
+            }
+
+            Int32Rect rect = new Int32Rect(0, 0, width, height);
+            int stride = (width * wbitmap.Format.BitsPerPixel + 7) / 8;
+            wbitmap.WritePixels(rect, pixels1d, stride, 0);
+
+            Image image = new Image()
+            {
+                Width = 200,
+                Height = 200,
+                Stretch = Stretch.None,
+                Margin = new Thickness(0),
+                Source = wbitmap
+            };
+            RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality);
+
+            uxIcoUniformGrid.Children.Add(image);
+        }
+
+        private void uxDefaultRadioButton_Checked(object sender, RoutedEventArgs e)
+        {
+            Uri srcPath;
+            RadioButton rb = e.Source as RadioButton;
+            if (rb is null)
+            {
+                return;
+            }
+
+            switch (rb.Tag.ToString())
+            {
+                case "Logo":
+                    srcPath = new Uri(Utility.ICO_PARENTFOLDER + Utility.ICO_LOGO, UriKind.Relative);
+                    uxLogoImage.Source = new BitmapImage(srcPath);
+                    mySettings.IsLogoDefault = true;
+                    break;
+                default:
+                    return;
+            }
+
+            mySettings.Save();
+
+            ModifyIcoContent((int)IcoElement.Logo);
+            //Display Ico
+            uxIcoUniformGrid.Children.Clear();
+            DisplayDWIcoContent();
+        }
+
+        private void uxCustomRadioButton_Checked(object sender, RoutedEventArgs e)
+        {
+            RadioButton rb = e.Source as RadioButton;
+            if (rb is null)
+            {
+                return;
+            }
+
+            switch (rb.Tag.ToString())
+            {
+                case "Logo":
+                    if (File.Exists(logoCustomImgPath))
+                    {
+                        Utility.LoadImageFromPath(uxLogoImage, logoCustomImgPath);
+                        mySettings.IsLogoDefault = false;
+
+                        ModifyIcoContent((int)IcoElement.Logo);
+                        //Display Ico
+                        uxIcoUniformGrid.Children.Clear();
+                        DisplayDWIcoContent();
+                    }
+                    break;
+                default:
+                    return;
+            }
+
+            mySettings.Save();
+        }
+
+        private void uxBrowseButton_Click(object sender, RoutedEventArgs e)
+        {
+            Button btn = e.Source as Button;
+            if (btn is null)
+            {
+                return;
+            }
+
+            var dlg = new CommonOpenFileDialog()
+            {
+                EnsureFileExists = true,
+                Title = "Select an image file",
+            };
+            dlg.Filters.Add(new CommonFileDialogFilter("BMP images", "*.bmp"));
+
+            if (dlg.ShowDialog() == CommonFileDialogResult.Ok)
+            {
+                if (!Utility.IsIcoLogoSizeQualified(dlg.FileName))
+                {
+                    MessageBox.Show(dlg.FileName + "\r\n" + "The resolution of the image must be 186x34",
+                                    "Incorrect Resolution", MessageBoxButton.OK, MessageBoxImage.Error);
+                    return;
+                }
+
+                switch (btn.Tag.ToString())
+                {
+                    case "Logo":
+                        uxLogoBrowseTextBox.Text = dlg.FileName;
+                        logoCustomImgPath = Utility.CUSTOM_DIRECTORY + Utility.ICO_LOGO;
+                        File.Copy(dlg.FileName, logoCustomImgPath, true);
+                        Utility.LoadImageFromPath(uxLogoImage, logoCustomImgPath);
+                        mySettings.IsLogoDefault = false;
+                        mySettings.LogoCustomImgPath = logoCustomImgPath;
+                        mySettings.LogoCustomImgPath_org = uxLogoBrowseTextBox.Text;
+
+                        ModifyIcoContent((int)IcoElement.Logo);
+                        //Display Ico
+                        uxIcoUniformGrid.Children.Clear();
+                        DisplayDWIcoContent();
+                        break;
+                    default:
+                        break;
+                }
+            }
+
+            mySettings.Save();
+        }
+    }
+}

+ 192 - 0
Phihong EVSE UI Tool.csproj

@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{F14ABC4B-9208-44A2-9A3C-FF1786DAD4AB}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <RootNamespace>Phihong_EVSE_UI_Tool</RootNamespace>
+    <AssemblyName>Phihong EVSE UI Tool</AssemblyName>
+    <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <WarningLevel>4</WarningLevel>
+    <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+    <Deterministic>true</Deterministic>
+    <NuGetPackageImportStamp>
+    </NuGetPackageImportStamp>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug\</OutputPath>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DebugType>pdbonly</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release\</OutputPath>
+    <DefineConstants>TRACE</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="FluentWPF, Version=0.8.0.6, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>packages\FluentWPF.0.8.0\lib\net45\FluentWPF.dll</HintPath>
+    </Reference>
+    <Reference Include="MaterialDesignColors, Version=1.2.7.1979, Culture=neutral, PublicKeyToken=df2a72020bd7962a, processorArchitecture=MSIL">
+      <HintPath>packages\MaterialDesignColors.1.2.7\lib\net45\MaterialDesignColors.dll</HintPath>
+    </Reference>
+    <Reference Include="MaterialDesignThemes.Wpf, Version=3.2.0.1979, Culture=neutral, PublicKeyToken=df2a72020bd7962a, processorArchitecture=MSIL">
+      <HintPath>packages\MaterialDesignThemes.3.2.0\lib\net45\MaterialDesignThemes.Wpf.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.WindowsAPICodePack, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>packages\Microsoft.WindowsAPICodePack-Core.1.1.0.2\lib\Microsoft.WindowsAPICodePack.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.WindowsAPICodePack.Shell, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>packages\Microsoft.WindowsAPICodePack-Shell.1.1.0.0\lib\Microsoft.WindowsAPICodePack.Shell.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.WindowsAPICodePack.ShellExtensions, Version=1.1.0.0, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>packages\Microsoft.WindowsAPICodePack-Shell.1.1.0.0\lib\Microsoft.WindowsAPICodePack.ShellExtensions.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="System.Net.Http" />
+    <Reference Include="System.Xaml">
+      <RequiredTargetFramework>4.0</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="WindowsBase" />
+    <Reference Include="PresentationCore" />
+    <Reference Include="PresentationFramework" />
+  </ItemGroup>
+  <ItemGroup>
+    <ApplicationDefinition Include="App.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </ApplicationDefinition>
+    <Page Include="AuthenticationUC.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="BuildIco.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="InitialIdleUC.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="MaintenanceUC.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="MainWindow.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Compile Include="App.xaml.cs">
+      <DependentUpon>App.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="AuthenticationUC.xaml.cs">
+      <DependentUpon>AuthenticationUC.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="BuildIco.xaml.cs">
+      <DependentUpon>BuildIco.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Enums.cs" />
+    <Compile Include="ImageList.cs" />
+    <Compile Include="ModifyIco.xaml.cs">
+      <DependentUpon>ModifyIco.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Utility.cs" />
+    <Compile Include="InitialIdleUC.xaml.cs">
+      <DependentUpon>InitialIdleUC.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="MaintenanceUC.xaml.cs">
+      <DependentUpon>MaintenanceUC.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="MainWindow.xaml.cs">
+      <DependentUpon>MainWindow.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
+    <Page Include="ModifyIco.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+    <Page Include="PlugChargingUC.xaml">
+      <SubType>Designer</SubType>
+      <Generator>MSBuild:Compile</Generator>
+    </Page>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="PlugChargingUC.xaml.cs">
+      <DependentUpon>PlugChargingUC.xaml</DependentUpon>
+    </Compile>
+    <Compile Include="Properties\AssemblyInfo.cs">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DesignTime>True</DesignTime>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+    </EmbeddedResource>
+    <None Include="packages.config" />
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="App.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="Background\0_init.bmp" />
+    <Resource Include="Background\1_idle.bmp" />
+    <Resource Include="Background\2_verify.bmp" />
+    <Resource Include="Background\3_carChkok.bmp" />
+    <Resource Include="Background\4_chkfail.bmp" />
+    <Resource Include="Background\5_plug.bmp" />
+    <Resource Include="Background\6_precharge.bmp" />
+    <Resource Include="Background\7_charging.bmp" />
+    <Resource Include="Background\8_complete.bmp" />
+    <Resource Include="Background\9_fix.bmp" />
+  </ItemGroup>
+  <ItemGroup>
+    <Folder Include="Output\" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="ICO\10_logo.bmp" />
+  </ItemGroup>
+  <ItemGroup>
+    <Resource Include="ICO\60.ICO" />
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <Import Project="packages\MaterialDesignThemes.3.2.0\build\MaterialDesignThemes.targets" Condition="Exists('packages\MaterialDesignThemes.3.2.0\build\MaterialDesignThemes.targets')" />
+  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
+    <PropertyGroup>
+      <ErrorText>此專案參考這部電腦上所缺少的 NuGet 套件。請啟用 NuGet 套件還原,以下載該套件。如需詳細資訊,請參閱 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的檔案是 {0}。</ErrorText>
+    </PropertyGroup>
+    <Error Condition="!Exists('packages\MaterialDesignThemes.3.2.0\build\MaterialDesignThemes.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\MaterialDesignThemes.3.2.0\build\MaterialDesignThemes.targets'))" />
+  </Target>
+</Project>

+ 25 - 0
Phihong EVSE UI Tool.sln

@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30517.126
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Phihong EVSE UI Tool", "Phihong EVSE UI Tool.csproj", "{F14ABC4B-9208-44A2-9A3C-FF1786DAD4AB}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{F14ABC4B-9208-44A2-9A3C-FF1786DAD4AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{F14ABC4B-9208-44A2-9A3C-FF1786DAD4AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F14ABC4B-9208-44A2-9A3C-FF1786DAD4AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F14ABC4B-9208-44A2-9A3C-FF1786DAD4AB}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {29A69144-C4D6-4C56-AFCD-8AD3C34F1472}
+	EndGlobalSection
+EndGlobal

+ 96 - 0
PlugChargingUC.xaml

@@ -0,0 +1,96 @@
+<UserControl x:Class="Phihong_EVSE_UI_Tool.PlugChargingUC"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:local="clr-namespace:Phihong_EVSE_UI_Tool"
+             xmlns:fd="clr-namespace:SourceChord.FluentWPF;assembly=FluentWPF"
+             mc:Ignorable="d" 
+             d:DesignHeight="1350" d:DesignWidth="880" FontSize="15" FontFamily="Segoe UI">
+    <UserControl.Resources>
+        <BooleanToVisibilityConverter x:Key="b2v"/>
+    </UserControl.Resources>
+    <Grid fd:PointerTracker.Enabled="True">
+        <ScrollViewer x:Name="uxContentScrollViewer" HorizontalContentAlignment="Center" PanningMode="VerticalOnly" IsDeferredScrollingEnabled="False" DataContext="{Binding}">
+            <StackPanel>
+                <!-- Plug -->
+                <TextBlock Text="Plug" FontSize="24" HorizontalAlignment="Left" Margin="22,6,0,8"/>
+                <TextBlock Text="Background picture for the Plug status (800x480)" HorizontalAlignment="Left" Margin="22,4,0,12"/>
+                <Image x:Name="uxPlugImage" Source="/Background/5_plug.bmp" HorizontalAlignment="Left" Margin="22,0,0,12" Height="192" Width="320" RenderOptions.BitmapScalingMode="HighQuality"/>
+                <StackPanel Orientation="Horizontal" Margin="22,8,370,12">
+                    <RadioButton x:Name="uxPlugDefaultRadioButton" Tag="Plug" GroupName="plugSelect" Margin="0,0,36,0" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxDefaultRadioButton_Checked">Default</RadioButton>
+                    <RadioButton x:Name="uxPlugCustomRadioButton" Tag="Plug" GroupName="plugSelect" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxCustomRadioButton_Checked">Customized</RadioButton>
+                </StackPanel>
+                <StackPanel x:Name="uxPlugCustomStackPanel" Orientation="Horizontal" Margin="22,0,450,12" 
+                            Visibility="{Binding IsChecked,ElementName=uxPlugCustomRadioButton,Converter={StaticResource b2v}}">
+                    <Button Tag="Plug" Content="Browse" HorizontalAlignment="Left" Width="90" Height="35" 
+                            Style="{StaticResource ButtonRevealStyle}" Click="uxBrowseButton_Click"/>
+                    <TextBox x:Name="uxPlugExploreTextBox" VerticalContentAlignment="Center" Margin="1,0,0,0" Width="270" Height="35" 
+                             Style="{StaticResource TextBoxRevealStyle}" Text="Please select an image file" IsReadOnly="True"/>
+                </StackPanel>
+                <!-- Precharge -->
+                <TextBlock Text="Precharge" FontSize="24" HorizontalAlignment="Left" Margin="22,18,0,8"/>
+                <TextBlock Text="Background picture for Precharge status (800x480)" HorizontalAlignment="Left" Margin="22,4,0,12"/>
+                <Image x:Name="uxPrechargeImage" Source="/Background/6_precharge.bmp" HorizontalAlignment="Left" Margin="22,0,0,12" Height="192" Width="320" RenderOptions.BitmapScalingMode="HighQuality"/>
+                <StackPanel Orientation="Horizontal" Margin="22,8,370,12">
+                    <RadioButton x:Name="uxPrechargeDefaultRadioButton" Tag="Precharge" GroupName="prechargeSelect" Margin="0,0,36,0" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxDefaultRadioButton_Checked">Default</RadioButton>
+                    <RadioButton x:Name="uxPrechargeCustomRadioButton" Tag="Precharge" GroupName="prechargeSelect" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxCustomRadioButton_Checked">Customized</RadioButton>
+                </StackPanel>
+                <StackPanel x:Name="uxPrechargeCustomStackPanel" Orientation="Horizontal" Margin="22,0,450,12" 
+                            Visibility="{Binding IsChecked,ElementName=uxPrechargeCustomRadioButton,Converter={StaticResource b2v}}">
+                    <Button Tag="Precharge" Content="Browse" HorizontalAlignment="Left" Width="90" Height="35" 
+                            Style="{StaticResource ButtonRevealStyle}" Click="uxBrowseButton_Click"/>
+                    <TextBox x:Name="uxPrechargeExploreTextBox" VerticalContentAlignment="Center" Margin="1,0,0,0" Width="270" Height="35" 
+                             Style="{StaticResource TextBoxRevealStyle}" Text="Please select an image file" IsReadOnly="True"/>
+                </StackPanel>
+                <!-- Charging -->
+                <TextBlock Text="Charging" FontSize="24" HorizontalAlignment="Left" Margin="22,18,0,8"/>
+                <TextBlock Text="Background picture for Charging status (800x480)" HorizontalAlignment="Left" Margin="22,4,0,12"/>
+                <Image x:Name="uxChargingImage" Source="/Background/7_charging.bmp" HorizontalAlignment="Left" Margin="22,0,0,12" Height="192" Width="320" RenderOptions.BitmapScalingMode="HighQuality"/>
+                <StackPanel Orientation="Horizontal" Margin="22,8,370,12">
+                    <RadioButton x:Name="uxChargingDefaultRadioButton" Tag="Charging" GroupName="chargingSelect" Margin="0,0,36,0" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxDefaultRadioButton_Checked">Default</RadioButton>
+                    <RadioButton x:Name="uxChargingCustomRadioButton" Tag="Charging" GroupName="chargingSelect" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxCustomRadioButton_Checked">Customized</RadioButton>
+                </StackPanel>
+                <StackPanel x:Name="uxChargingCustomStackPanel" Orientation="Horizontal" Margin="22,0,450,12" 
+                            Visibility="{Binding IsChecked,ElementName=uxChargingCustomRadioButton,Converter={StaticResource b2v}}">
+                    <Button Tag="Charging" Content="Browse" HorizontalAlignment="Left" Width="90" Height="35" 
+                            Style="{StaticResource ButtonRevealStyle}" Click="uxBrowseButton_Click"/>
+                    <TextBox x:Name="uxChargingExploreTextBox" VerticalContentAlignment="Center" Margin="1,0,0,0" Width="270" Height="35" 
+                             Style="{StaticResource TextBoxRevealStyle}" Text="Please select an image file" IsReadOnly="True"/>
+                </StackPanel>
+                <!-- Complete -->
+                <TextBlock Text="Complete" FontSize="24" HorizontalAlignment="Left" Margin="22,18,0,8"/>
+                <TextBlock Text="Background picture for Complete status (800x480)" HorizontalAlignment="Left" Margin="22,4,0,12"/>
+                <Image x:Name="uxCompleteImage" Source="/Background/8_complete.bmp" HorizontalAlignment="Left" Margin="22,0,0,12" Height="192" Width="320" RenderOptions.BitmapScalingMode="HighQuality"/>
+                <StackPanel Orientation="Horizontal" Margin="22,8,370,12">
+                    <RadioButton x:Name="uxCompleteDefaultRadioButton" Tag="Complete" GroupName="completeSelect" Margin="0,0,36,0" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxDefaultRadioButton_Checked">Default</RadioButton>
+                    <RadioButton x:Name="uxCompleteCustomRadioButton" Tag="Complete" GroupName="completeSelect" Style="{StaticResource MaterialDesignRadioButton}" 
+                                 Background="{Binding Path=(fd:AccentColors.ImmersiveSystemAccentBrush)}"
+                                 Checked="uxCustomRadioButton_Checked">Customized</RadioButton>
+                </StackPanel>
+                <StackPanel x:Name="uxCompleteCustomStackPanel" Orientation="Horizontal" Margin="22,0,450,12" 
+                            Visibility="{Binding IsChecked,ElementName=uxCompleteCustomRadioButton,Converter={StaticResource b2v}}">
+                    <Button Tag="Complete" Content="Browse" HorizontalAlignment="Left" Width="90" Height="35" 
+                            Style="{StaticResource ButtonRevealStyle}" Click="uxBrowseButton_Click"/>
+                    <TextBox x:Name="uxCompleteExploreTextBox" VerticalContentAlignment="Center" Margin="1,0,0,0" Width="270" Height="35" 
+                             Style="{StaticResource TextBoxRevealStyle}" Text="Please select an image file" IsReadOnly="True"/>
+                </StackPanel>
+                <StackPanel Margin="22,0,450,10" Height="15"/>
+            </StackPanel>
+        </ScrollViewer>
+    </Grid>
+</UserControl>

+ 221 - 0
PlugChargingUC.xaml.cs

@@ -0,0 +1,221 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using Microsoft.WindowsAPICodePack.Dialogs;
+
+namespace Phihong_EVSE_UI_Tool
+{
+    /// <summary>
+    /// PlugChargingUC.xaml 的互動邏輯
+    /// </summary>
+    public partial class PlugChargingUC : UserControl
+    {
+        private Properties.Settings mySettings = Properties.Settings.Default;
+        private string plugCustomImgPath;
+        private string prechargeCustomImgPath;
+        private string chargingCustomImgPath;
+        private string completeCustomImgPath;
+
+        public PlugChargingUC()
+        {
+            InitializeComponent();
+
+            plugCustomImgPath = mySettings.PlugCustomImgPath;
+            uxPlugDefaultRadioButton.IsChecked = mySettings.IsPlugDefault;
+            uxPlugCustomRadioButton.IsChecked = !mySettings.IsPlugDefault;
+            uxPlugExploreTextBox.Text = mySettings.PlugCustomImgPath_org;
+
+            prechargeCustomImgPath = mySettings.PreChargeCustomImgPath;
+            uxPrechargeDefaultRadioButton.IsChecked = mySettings.IsPreChargeDefault;
+            uxPrechargeCustomRadioButton.IsChecked = !mySettings.IsPreChargeDefault;
+            uxPrechargeExploreTextBox.Text = mySettings.PreChargeCustomImgPath_org;
+
+            chargingCustomImgPath = mySettings.ChargingCustomImgPath;
+            uxChargingDefaultRadioButton.IsChecked = mySettings.IsChargingDefault;
+            uxChargingCustomRadioButton.IsChecked = !mySettings.IsChargingDefault;
+            uxChargingExploreTextBox.Text = mySettings.ChargingCustomImgPath_org;
+
+            completeCustomImgPath = mySettings.CompleteCustomImgPath;
+            uxCompleteDefaultRadioButton.IsChecked = mySettings.IsCompleteDefault;
+            uxCompleteCustomRadioButton.IsChecked = !mySettings.IsCompleteDefault;
+            uxCompleteExploreTextBox.Text = mySettings.CompleteCustomImgPath_org;
+        }
+
+        private void uxDefaultRadioButton_Checked(object sender, RoutedEventArgs e)
+        {
+            Uri srcPath;
+            RadioButton rb = e.Source as RadioButton;
+            if (rb is null)
+            {
+                return;
+            }
+
+            switch (rb.Tag.ToString())
+            {
+                case "Plug":
+                    srcPath = new Uri(Utility.BG_PARENTFOLDER + Utility.BG_PLUG, UriKind.Relative);
+                    uxPlugImage.Source = new BitmapImage(srcPath);
+                    Utility.CopyFileFromResource(srcPath, Utility.OUTPUT_DIRECTORY + Utility.BG_PLUG);
+                    mySettings.IsPlugDefault = true;
+                    break;
+                case "Precharge":
+                    srcPath = new Uri(Utility.BG_PARENTFOLDER + Utility.BG_PRECHARGE, UriKind.Relative);
+                    uxPrechargeImage.Source = new BitmapImage(srcPath);
+                    Utility.CopyFileFromResource(srcPath, Utility.OUTPUT_DIRECTORY + Utility.BG_PRECHARGE);
+                    mySettings.IsPreChargeDefault = true;
+                    break;
+                case "Charging":
+                    srcPath = new Uri(Utility.BG_PARENTFOLDER + Utility.BG_CHARGING, UriKind.Relative);
+                    uxChargingImage.Source = new BitmapImage(srcPath);
+                    Utility.CopyFileFromResource(srcPath, Utility.OUTPUT_DIRECTORY + Utility.BG_CHARGING);
+                    mySettings.IsChargingDefault = true;
+                    break;
+                case "Complete":
+                    srcPath = new Uri(Utility.BG_PARENTFOLDER + Utility.BG_COMPLETE, UriKind.Relative);
+                    uxCompleteImage.Source = new BitmapImage(srcPath);
+                    Utility.CopyFileFromResource(srcPath, Utility.OUTPUT_DIRECTORY + Utility.BG_COMPLETE);
+                    mySettings.IsCompleteDefault = true;
+                    break;
+                default:
+                    return;
+            }
+
+            mySettings.Save();
+        }
+
+        private void uxCustomRadioButton_Checked(object sender, RoutedEventArgs e)
+        {
+            RadioButton rb = e.Source as RadioButton;
+            if (rb is null)
+            {
+                return;
+            }
+
+            switch (rb.Tag.ToString())
+            {
+                case "Plug":
+                    if (File.Exists(plugCustomImgPath))
+                    {
+                        Utility.LoadImageFromPath(uxPlugImage, plugCustomImgPath);
+                        File.Copy(plugCustomImgPath, Utility.OUTPUT_DIRECTORY + Utility.BG_PLUG, true);
+                        mySettings.IsPlugDefault = false;
+                    }
+                    break;
+                case "Precharge":
+                    if (File.Exists(prechargeCustomImgPath))
+                    {
+                        Utility.LoadImageFromPath(uxPrechargeImage, prechargeCustomImgPath);
+                        File.Copy(prechargeCustomImgPath, Utility.OUTPUT_DIRECTORY + Utility.BG_PRECHARGE, true);
+                        mySettings.IsPreChargeDefault = false;
+                    }
+                    break;
+                case "Charging":
+                    if (File.Exists(chargingCustomImgPath))
+                    {
+                        Utility.LoadImageFromPath(uxChargingImage, chargingCustomImgPath);
+                        File.Copy(chargingCustomImgPath, Utility.OUTPUT_DIRECTORY + Utility.BG_CHARGING, true);
+                        mySettings.IsChargingDefault = false;
+                    }
+                    break;
+                case "Complete":
+                    if (File.Exists(completeCustomImgPath))
+                    {
+                        Utility.LoadImageFromPath(uxCompleteImage, completeCustomImgPath);
+                        File.Copy(completeCustomImgPath, Utility.OUTPUT_DIRECTORY + Utility.BG_COMPLETE, true);
+                        mySettings.IsCompleteDefault = false;
+                    }
+                    break;
+                default:
+                    return;
+            }
+
+            mySettings.Save();
+        }
+
+        private void uxBrowseButton_Click(object sender, RoutedEventArgs e)
+        {
+            Button btn = e.Source as Button;
+            if (btn is null)
+            {
+                return;
+            }
+
+            var dlg = new CommonOpenFileDialog()
+            {
+                EnsureFileExists = true,
+                Title = "Select an image file",
+            };
+            dlg.Filters.Add(new CommonFileDialogFilter("BMP images", "*.bmp"));
+
+            if (dlg.ShowDialog() == CommonFileDialogResult.Ok)
+            {
+                if (!Utility.IsBackgroundImageSizeQualified(dlg.FileName))
+                {
+                    MessageBox.Show(dlg.FileName + "\r\n" + "The resolution of the image must be 800x480",
+                                    "Incorrect Resolution", MessageBoxButton.OK, MessageBoxImage.Error);
+                    return;
+                }
+
+                switch (btn.Tag.ToString())
+                {
+                    case "Plug":
+                        uxPlugExploreTextBox.Text = dlg.FileName;
+                        plugCustomImgPath = Utility.CUSTOM_DIRECTORY + Utility.BG_PLUG;
+                        File.Copy(dlg.FileName, plugCustomImgPath, true);
+                        File.Copy(dlg.FileName, Utility.OUTPUT_DIRECTORY + Utility.BG_PLUG, true);
+                        Utility.LoadImageFromPath(uxPlugImage, plugCustomImgPath);
+                        mySettings.IsPlugDefault = false;
+                        mySettings.PlugCustomImgPath = plugCustomImgPath;
+                        mySettings.PlugCustomImgPath_org = uxPlugExploreTextBox.Text;
+                        break;
+                    case "Precharge":
+                        uxPrechargeExploreTextBox.Text = dlg.FileName;
+                        prechargeCustomImgPath = Utility.CUSTOM_DIRECTORY + Utility.BG_PRECHARGE;
+                        File.Copy(dlg.FileName, prechargeCustomImgPath, true);
+                        File.Copy(dlg.FileName, Utility.OUTPUT_DIRECTORY + Utility.BG_PRECHARGE, true);
+                        Utility.LoadImageFromPath(uxPrechargeImage, prechargeCustomImgPath);
+                        mySettings.IsPreChargeDefault = false;
+                        mySettings.PreChargeCustomImgPath = prechargeCustomImgPath;
+                        mySettings.PreChargeCustomImgPath_org = uxPrechargeExploreTextBox.Text;
+                        break;
+                    case "Charging":
+                        uxChargingExploreTextBox.Text = dlg.FileName;
+                        chargingCustomImgPath = Utility.CUSTOM_DIRECTORY + Utility.BG_CHARGING;
+                        File.Copy(dlg.FileName, chargingCustomImgPath, true);
+                        File.Copy(dlg.FileName, Utility.OUTPUT_DIRECTORY + Utility.BG_CHARGING, true);
+                        Utility.LoadImageFromPath(uxChargingImage, chargingCustomImgPath);
+                        mySettings.IsChargingDefault = false;
+                        mySettings.ChargingCustomImgPath = chargingCustomImgPath;
+                        mySettings.ChargingCustomImgPath_org = uxChargingExploreTextBox.Text;
+                        break;
+                    case "Complete":
+                        uxCompleteExploreTextBox.Text = dlg.FileName;
+                        completeCustomImgPath = Utility.CUSTOM_DIRECTORY + Utility.BG_COMPLETE;
+                        File.Copy(dlg.FileName, completeCustomImgPath, true);
+                        File.Copy(dlg.FileName, Utility.OUTPUT_DIRECTORY + Utility.BG_COMPLETE, true);
+                        Utility.LoadImageFromPath(uxCompleteImage, completeCustomImgPath);
+                        mySettings.IsCompleteDefault = false;
+                        mySettings.CompleteCustomImgPath = completeCustomImgPath;
+                        mySettings.CompleteCustomImgPath_org = uxCompleteExploreTextBox.Text;
+                        break;
+                    default:
+                        break;
+                }
+            }
+
+            mySettings.Save();
+        }
+    }
+}

+ 55 - 0
Properties/AssemblyInfo.cs

@@ -0,0 +1,55 @@
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// 組件的一般資訊是由下列的屬性集控制。
+// 變更這些屬性的值即可修改組件的相關
+// 資訊。
+[assembly: AssemblyTitle("Phihong EVSE UI Tool")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Phihong EVSE UI Tool")]
+[assembly: AssemblyCopyright("Copyright ©  2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 將 ComVisible 設為 false 可對 COM 元件隱藏
+// 組件中的類型。若必須從 COM 存取此組件中的類型,
+// 的類型,請在該類型上將 ComVisible 屬性設定為 true。
+[assembly: ComVisible(false)]
+
+//若要開始建置可當地語系化的應用程式,請在
+//.csproj 檔案中的 <UICulture>CultureYouAreCodingWith</UICulture>
+//<UICulture>CultureYouAreCodingWith</UICulture>。例如,如果原始程式檔使用美式英文, 
+//請將 <UICulture> 設為 en-US。然後取消註解下列
+//NeutralResourceLanguage 屬性。在下一行中更新 "en-US",
+//以符合專案檔中的 UICulture 設定。
+
+//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+
+
+[assembly: ThemeInfo(
+    ResourceDictionaryLocation.None, //主題特定資源字典的位置
+                                     //(在頁面中找不到時使用,
+                                     // 或應用程式資源字典中找不到資源時)
+    ResourceDictionaryLocation.SourceAssembly //泛型資源字典的位置
+                                              //(在頁面中找不到時使用,
+                                              // 或是應用程式或任何主題特定資源字典中找不到資源時)
+)]
+
+
+// 組件的版本資訊由下列四個值所組成:
+//
+//      主要版本
+//      次要版本
+//      組建編號
+//      修訂
+//
+// 您可以指定所有的值,也可以使用 '*' 將組建和修訂編號
+// 設為預設,如下所示:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]

+ 71 - 0
Properties/Resources.Designer.cs

@@ -0,0 +1,71 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     這段程式碼是由工具產生的。
+//     執行階段版本:4.0.30319.42000
+//
+//     變更這個檔案可能會導致不正確的行為,而且如果已重新產生
+//     程式碼,則會遺失變更。
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Phihong_EVSE_UI_Tool.Properties
+{
+
+
+    /// <summary>
+    ///   用於查詢當地語系化字串等的強類型資源類別
+    /// </summary>
+    // 這個類別是自動產生的,是利用 StronglyTypedResourceBuilder
+    // 類別透過 ResGen 或 Visual Studio 這類工具產生。
+    // 若要加入或移除成員,請編輯您的 .ResX 檔,然後重新執行 ResGen
+    // (利用 /str 選項),或重建您的 VS 專案。
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources
+    {
+
+        private static global::System.Resources.ResourceManager resourceMan;
+
+        private static global::System.Globalization.CultureInfo resourceCulture;
+
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources()
+        {
+        }
+
+        /// <summary>
+        ///   傳回這個類別使用的快取的 ResourceManager 執行個體。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager
+        {
+            get
+            {
+                if ((resourceMan == null))
+                {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Phihong_EVSE_UI_Tool.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+
+        /// <summary>
+        ///   覆寫目前執行緒的 CurrentUICulture 屬性,對象是所有
+        ///   使用這個強類型資源類別的資源查閱。
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture
+        {
+            get
+            {
+                return resourceCulture;
+            }
+            set
+            {
+                resourceCulture = value;
+            }
+        }
+    }
+}

+ 117 - 0
Properties/Resources.resx

@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+</root>

+ 422 - 0
Properties/Settings.Designer.cs

@@ -0,0 +1,422 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     這段程式碼是由工具產生的。
+//     執行階段版本:4.0.30319.42000
+//
+//     對這個檔案所做的變更可能會造成錯誤的行為,而且如果重新產生程式碼,
+//     變更將會遺失。
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Phihong_EVSE_UI_Tool.Properties {
+    
+    
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.7.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+        
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+        
+        public static Settings Default {
+            get {
+                return defaultInstance;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("True")]
+        public bool IsInitialDefault {
+            get {
+                return ((bool)(this["IsInitialDefault"]));
+            }
+            set {
+                this["IsInitialDefault"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string InitialCustomImgPath {
+            get {
+                return ((string)(this["InitialCustomImgPath"]));
+            }
+            set {
+                this["InitialCustomImgPath"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string InitialCustomImgPath_org {
+            get {
+                return ((string)(this["InitialCustomImgPath_org"]));
+            }
+            set {
+                this["InitialCustomImgPath_org"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("True")]
+        public bool IsIdleDefault {
+            get {
+                return ((bool)(this["IsIdleDefault"]));
+            }
+            set {
+                this["IsIdleDefault"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string IdleCustomImgPath {
+            get {
+                return ((string)(this["IdleCustomImgPath"]));
+            }
+            set {
+                this["IdleCustomImgPath"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string IdleCustomImgPath_org {
+            get {
+                return ((string)(this["IdleCustomImgPath_org"]));
+            }
+            set {
+                this["IdleCustomImgPath_org"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("True")]
+        public bool IsVerifyDefault {
+            get {
+                return ((bool)(this["IsVerifyDefault"]));
+            }
+            set {
+                this["IsVerifyDefault"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string VerifyCustomImgPath {
+            get {
+                return ((string)(this["VerifyCustomImgPath"]));
+            }
+            set {
+                this["VerifyCustomImgPath"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string VerifyCustomImgPath_org {
+            get {
+                return ((string)(this["VerifyCustomImgPath_org"]));
+            }
+            set {
+                this["VerifyCustomImgPath_org"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("True")]
+        public bool IsVerifyOkDefault {
+            get {
+                return ((bool)(this["IsVerifyOkDefault"]));
+            }
+            set {
+                this["IsVerifyOkDefault"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string VerifyOkCustomImgPath {
+            get {
+                return ((string)(this["VerifyOkCustomImgPath"]));
+            }
+            set {
+                this["VerifyOkCustomImgPath"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string VerifyOkCustomImgPath_org {
+            get {
+                return ((string)(this["VerifyOkCustomImgPath_org"]));
+            }
+            set {
+                this["VerifyOkCustomImgPath_org"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("True")]
+        public bool IsVerifyFailDefault {
+            get {
+                return ((bool)(this["IsVerifyFailDefault"]));
+            }
+            set {
+                this["IsVerifyFailDefault"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string VerifyFailCustomImgPath {
+            get {
+                return ((string)(this["VerifyFailCustomImgPath"]));
+            }
+            set {
+                this["VerifyFailCustomImgPath"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string VerifyFailCustomImgPath_org {
+            get {
+                return ((string)(this["VerifyFailCustomImgPath_org"]));
+            }
+            set {
+                this["VerifyFailCustomImgPath_org"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("True")]
+        public bool IsPlugDefault {
+            get {
+                return ((bool)(this["IsPlugDefault"]));
+            }
+            set {
+                this["IsPlugDefault"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string PlugCustomImgPath {
+            get {
+                return ((string)(this["PlugCustomImgPath"]));
+            }
+            set {
+                this["PlugCustomImgPath"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string PlugCustomImgPath_org {
+            get {
+                return ((string)(this["PlugCustomImgPath_org"]));
+            }
+            set {
+                this["PlugCustomImgPath_org"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("True")]
+        public bool IsPreChargeDefault {
+            get {
+                return ((bool)(this["IsPreChargeDefault"]));
+            }
+            set {
+                this["IsPreChargeDefault"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string PreChargeCustomImgPath {
+            get {
+                return ((string)(this["PreChargeCustomImgPath"]));
+            }
+            set {
+                this["PreChargeCustomImgPath"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string PreChargeCustomImgPath_org {
+            get {
+                return ((string)(this["PreChargeCustomImgPath_org"]));
+            }
+            set {
+                this["PreChargeCustomImgPath_org"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("True")]
+        public bool IsChargingDefault {
+            get {
+                return ((bool)(this["IsChargingDefault"]));
+            }
+            set {
+                this["IsChargingDefault"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string ChargingCustomImgPath {
+            get {
+                return ((string)(this["ChargingCustomImgPath"]));
+            }
+            set {
+                this["ChargingCustomImgPath"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string ChargingCustomImgPath_org {
+            get {
+                return ((string)(this["ChargingCustomImgPath_org"]));
+            }
+            set {
+                this["ChargingCustomImgPath_org"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("True")]
+        public bool IsCompleteDefault {
+            get {
+                return ((bool)(this["IsCompleteDefault"]));
+            }
+            set {
+                this["IsCompleteDefault"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string CompleteCustomImgPath {
+            get {
+                return ((string)(this["CompleteCustomImgPath"]));
+            }
+            set {
+                this["CompleteCustomImgPath"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string CompleteCustomImgPath_org {
+            get {
+                return ((string)(this["CompleteCustomImgPath_org"]));
+            }
+            set {
+                this["CompleteCustomImgPath_org"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("True")]
+        public bool IsMaintainDefault {
+            get {
+                return ((bool)(this["IsMaintainDefault"]));
+            }
+            set {
+                this["IsMaintainDefault"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string MaintainCustomImgPath {
+            get {
+                return ((string)(this["MaintainCustomImgPath"]));
+            }
+            set {
+                this["MaintainCustomImgPath"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string MaintainCustomImgPath_org {
+            get {
+                return ((string)(this["MaintainCustomImgPath_org"]));
+            }
+            set {
+                this["MaintainCustomImgPath_org"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("True")]
+        public bool IsLogoDefault {
+            get {
+                return ((bool)(this["IsLogoDefault"]));
+            }
+            set {
+                this["IsLogoDefault"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string LogoCustomImgPath {
+            get {
+                return ((string)(this["LogoCustomImgPath"]));
+            }
+            set {
+                this["LogoCustomImgPath"] = value;
+            }
+        }
+        
+        [global::System.Configuration.UserScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string LogoCustomImgPath_org {
+            get {
+                return ((string)(this["LogoCustomImgPath_org"]));
+            }
+            set {
+                this["LogoCustomImgPath_org"] = value;
+            }
+        }
+    }
+}

+ 105 - 0
Properties/Settings.settings

@@ -0,0 +1,105 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="Phihong_EVSE_UI_Tool.Properties" GeneratedClassName="Settings">
+  <Profiles />
+  <Settings>
+    <Setting Name="IsInitialDefault" Type="System.Boolean" Scope="User">
+      <Value Profile="(Default)">True</Value>
+    </Setting>
+    <Setting Name="InitialCustomImgPath" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="InitialCustomImgPath_org" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="IsIdleDefault" Type="System.Boolean" Scope="User">
+      <Value Profile="(Default)">True</Value>
+    </Setting>
+    <Setting Name="IdleCustomImgPath" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="IdleCustomImgPath_org" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="IsVerifyDefault" Type="System.Boolean" Scope="User">
+      <Value Profile="(Default)">True</Value>
+    </Setting>
+    <Setting Name="VerifyCustomImgPath" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="VerifyCustomImgPath_org" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="IsVerifyOkDefault" Type="System.Boolean" Scope="User">
+      <Value Profile="(Default)">True</Value>
+    </Setting>
+    <Setting Name="VerifyOkCustomImgPath" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="VerifyOkCustomImgPath_org" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="IsVerifyFailDefault" Type="System.Boolean" Scope="User">
+      <Value Profile="(Default)">True</Value>
+    </Setting>
+    <Setting Name="VerifyFailCustomImgPath" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="VerifyFailCustomImgPath_org" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="IsPlugDefault" Type="System.Boolean" Scope="User">
+      <Value Profile="(Default)">True</Value>
+    </Setting>
+    <Setting Name="PlugCustomImgPath" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="PlugCustomImgPath_org" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="IsPreChargeDefault" Type="System.Boolean" Scope="User">
+      <Value Profile="(Default)">True</Value>
+    </Setting>
+    <Setting Name="PreChargeCustomImgPath" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="PreChargeCustomImgPath_org" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="IsChargingDefault" Type="System.Boolean" Scope="User">
+      <Value Profile="(Default)">True</Value>
+    </Setting>
+    <Setting Name="ChargingCustomImgPath" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="ChargingCustomImgPath_org" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="IsCompleteDefault" Type="System.Boolean" Scope="User">
+      <Value Profile="(Default)">True</Value>
+    </Setting>
+    <Setting Name="CompleteCustomImgPath" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="CompleteCustomImgPath_org" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="IsMaintainDefault" Type="System.Boolean" Scope="User">
+      <Value Profile="(Default)">True</Value>
+    </Setting>
+    <Setting Name="MaintainCustomImgPath" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="MaintainCustomImgPath_org" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="IsLogoDefault" Type="System.Boolean" Scope="User">
+      <Value Profile="(Default)">True</Value>
+    </Setting>
+    <Setting Name="LogoCustomImgPath" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+    <Setting Name="LogoCustomImgPath_org" Type="System.String" Scope="User">
+      <Value Profile="(Default)" />
+    </Setting>
+  </Settings>
+</SettingsFile>

+ 153 - 0
Utility.cs

@@ -0,0 +1,153 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+
+namespace Phihong_EVSE_UI_Tool
+{
+    public static class Utility
+    {
+        public const string BG_PARENTFOLDER = @"Background\";
+
+        public const string BG_INITIAL = "0_init.bmp";
+        public const string BG_IDLE = "1_idle.bmp";
+        public const string BG_VERIFY = "2_verify.bmp";
+        public const string BG_VERIFYOK = "3_carChkok.bmp";
+        public const string BG_VERIFYFAIL = "4_chkfail.bmp";
+        public const string BG_PLUG = "5_plug.bmp";
+        public const string BG_PRECHARGE = "6_precharge.bmp";
+        public const string BG_CHARGING = "7_charging.bmp";
+        public const string BG_COMPLETE = "8_complete.bmp";
+        public const string BG_MAINTAIN = "9_fix.bmp";
+
+        public const string ICO_PARENTFOLDER = @"ICO\";
+
+        public const string ICO_LOGO = "10_logo.bmp";
+        public const string ICO_NAME = "60.ICO";
+
+        public static readonly string OUTPUT_DIRECTORY = Environment.CurrentDirectory + @"\Output\";
+        public static readonly string CUSTOM_DIRECTORY = Environment.CurrentDirectory + @"\CustomTemp\";
+
+        public static void CopyFileFromResource(Uri uriString, string destPath)
+        {
+            using (var resource = Application.GetResourceStream(uriString).Stream)
+            {
+                using (var file = new FileStream(destPath, FileMode.Create, FileAccess.Write))
+                {
+                    resource.CopyTo(file);
+                }
+            }
+        }
+
+        public static void LoadImageFromPath(Image imgControl, string path)
+        {
+            FileStream fstream = new FileStream(path, FileMode.Open);
+
+            BitmapImage bitmap = new BitmapImage();
+            bitmap.BeginInit();
+            bitmap.StreamSource = fstream;
+            bitmap.CacheOption = BitmapCacheOption.OnLoad;
+            bitmap.EndInit();
+            fstream.Close();
+
+            imgControl.BeginInit();
+            imgControl.Source = bitmap;
+            imgControl.EndInit();
+        }
+
+        public static bool IsBackgroundImageSizeQualified(string path)
+        {
+            FileStream fstream = new FileStream(path, FileMode.Open);
+
+            BitmapImage bitmap = new BitmapImage();
+            bitmap.BeginInit();
+            bitmap.StreamSource = fstream;
+            bitmap.CacheOption = BitmapCacheOption.OnLoad;
+            bitmap.EndInit();
+            fstream.Close();
+
+            if (bitmap.PixelWidth != 800 || bitmap.PixelHeight != 480)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        public static bool IsIcoImageSizeQualified(string path)
+        {
+            FileStream fstream = new FileStream(path, FileMode.Open);
+
+            BitmapImage bitmap = new BitmapImage();
+            bitmap.BeginInit();
+            bitmap.StreamSource = fstream;
+            bitmap.CacheOption = BitmapCacheOption.OnLoad;
+            bitmap.EndInit();
+            fstream.Close();
+
+            if (bitmap.PixelWidth > 255 || bitmap.PixelHeight > 255)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        public static bool IsIcoLogoSizeQualified(string path)
+        {
+            FileStream fstream = new FileStream(path, FileMode.Open);
+
+            BitmapImage bitmap = new BitmapImage();
+            bitmap.BeginInit();
+            bitmap.StreamSource = fstream;
+            bitmap.CacheOption = BitmapCacheOption.OnLoad;
+            bitmap.EndInit();
+            fstream.Close();
+
+            if (bitmap.PixelWidth != 186 || bitmap.PixelHeight != 34)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        public static void ConvertToBitmap565Array(string path, out int width, out int height, out byte[] pixels)
+        {
+            FileStream fstream = new FileStream(path, FileMode.Open);
+
+            BitmapImage bitmap = new BitmapImage();
+            bitmap.BeginInit();
+            bitmap.StreamSource = fstream;
+            bitmap.CacheOption = BitmapCacheOption.OnLoad;
+            bitmap.EndInit();
+            fstream.Close();
+
+            FormatConvertedBitmap newFormated = new FormatConvertedBitmap();
+            newFormated.BeginInit();
+            newFormated.Source = bitmap;
+            newFormated.DestinationFormat = PixelFormats.Bgr565;
+            newFormated.EndInit();
+
+            width = newFormated.PixelWidth;
+            height = newFormated.PixelHeight;
+
+            var stride = (newFormated.PixelWidth * newFormated.Format.BitsPerPixel + 7) / 8;
+            pixels = new byte[newFormated.PixelHeight * stride];
+            newFormated.CopyPixels(pixels, stride, 0);
+
+            for (int i = 0; i < pixels.Length; i += 2)
+            {
+                byte temp = pixels[i];
+                pixels[i] = pixels[i + 1];
+                pixels[i + 1] = temp;
+            }
+        }
+    }
+}

+ 8 - 0
packages.config

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="FluentWPF" version="0.8.0" targetFramework="net472" />
+  <package id="MaterialDesignColors" version="1.2.7" targetFramework="net472" />
+  <package id="MaterialDesignThemes" version="3.2.0" targetFramework="net472" />
+  <package id="Microsoft.WindowsAPICodePack-Core" version="1.1.0.2" targetFramework="net472" />
+  <package id="Microsoft.WindowsAPICodePack-Shell" version="1.1.0.0" targetFramework="net472" />
+</packages>