OSDN Git Service

Reside in the system tray if specified
authorKazuhiro <fujieda@users.osdn.me>
Sun, 8 May 2016 10:31:52 +0000 (19:31 +0900)
committerKazuhiro Fujieda <fujieda@users.osdn.me>
Fri, 23 Sep 2016 14:01:35 +0000 (23:01 +0900)
BurageSnap/BurageSnap.csproj
BurageSnap/Config.cs
BurageSnap/MainWindow.xaml
BurageSnap/MainWindow.xaml.cs
BurageSnap/MainWindowViewModel.cs
BurageSnap/NotifyIconWrapper.cs [new file with mode: 0644]
BurageSnap/OptionContent.cs
BurageSnap/OptionView.xaml
BurageSnap/Properties/Resources.Designer.cs
BurageSnap/Properties/Resources.ja.resx
BurageSnap/Properties/Resources.resx

index 7e8aae7..251fa01 100644 (file)
@@ -94,6 +94,7 @@
     <Compile Include="KeyModifier.cs" />
     <Compile Include="MetroPopupWindowAction.cs" />
     <Compile Include="NeuQuant.cs" />
+    <Compile Include="NotifyIconWrapper.cs" />
     <Compile Include="OptionContent.cs" />
     <Compile Include="OptionView.xaml.cs">
       <DependentUpon>OptionView.xaml</DependentUpon>
index 60231b8..20b008c 100644 (file)
@@ -31,6 +31,8 @@ namespace BurageSnap
         private static readonly string BaseDir = AppDomain.CurrentDomain.BaseDirectory;
         public Point Location { get; set; } = new Point(double.MinValue, double.MinValue);
         public bool TopMost { get; set; }
+        public bool ResideInSystemTray { get; set; }
+        public WindowState WindowState { get; set; }
         public int Interval { get; set; } = 200;
         public int RingBuffer { get; set; } = 25;
 
index 25a11f8..4469c06 100644 (file)
@@ -10,7 +10,8 @@
         xmlns:properties="clr-namespace:BurageSnap.Properties"
         mc:Ignorable="d"
         Style="{StaticResource WindowStyle}"
-        Title="BurageSnap" Height="114" Width="169" ResizeMode="CanMinimize" Icon="app.ico">
+        Title="BurageSnap" Height="114" Width="169" ResizeMode="CanMinimize" Icon="app.ico"
+        ShowInTaskbar="{Binding ShowInTaskbar}" WindowState="{Binding WindowState}">
     <controls:MetroWindow.DataContext>
         <local:MainWindowViewModel/>
     </controls:MetroWindow.DataContext>
         <Style TargetType="Button" BasedOn="{StaticResource ButtonStyle}"/>
     </Window.Resources>
     <Grid>
+        <local:NotifyIconWrapper Text="BurageSnap">
+            <i:Interaction.Triggers>
+                <i:EventTrigger EventName="OpenSelected">
+                    <i:InvokeCommandAction Command="{Binding NotifyIconOpenCommand}"/>
+                </i:EventTrigger>
+                <i:EventTrigger EventName="ExitSelected">
+                    <i:InvokeCommandAction Command="{Binding NotifyIconExitCommand}"/>
+                </i:EventTrigger>
+            </i:Interaction.Triggers>
+        </local:NotifyIconWrapper>
         <Button x:Name="buttonCapture" Content="{Binding CaptureButtonText}" HorizontalAlignment="Left" Margin="8,8,0,0" VerticalAlignment="Top" Width="77" Height="51" FontSize="12" Command="{Binding CaptureCommand}"/>
         <Button x:Name="buttonBrowse" HorizontalAlignment="Left" Margin="93,9,0,0" VerticalAlignment="Top" Width="31" Height="31" Command="{Binding BrowseCommand}">
             <Image Source="folder_open.ico"/>
     </Grid>
     <i:Interaction.Triggers>
         <i:EventTrigger EventName="Loaded">
-            <i:InvokeCommandAction Command="{Binding LoadedCommand}"/>
+            <prism:InvokeCommandAction Command="{Binding LoadedCommand}"/>
         </i:EventTrigger>
         <i:EventTrigger EventName="Closing">
-            <i:InvokeCommandAction Command="{Binding ClosingCommand}"/>
+            <prism:InvokeCommandAction Command="{Binding ClosingCommand}"/>
         </i:EventTrigger>
         <prism:InteractionRequestTrigger SourceObject="{Binding ConfirmationRequest, Mode=OneWay}">
             <local:MetroPopupWindowAction IsModal="True" CenterOverAssociatedObject="True" Owner="{Binding ElementName=mainWindow}">
index ca60214..a449285 100644 (file)
@@ -10,4 +10,4 @@
             InitializeComponent();
         }
     }
-}
+}
\ No newline at end of file
index eb6b644..f2e661c 100644 (file)
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 using System.Reflection;
+using System.ComponentModel;
 using System.Windows;
 using System.Windows.Input;
 using BurageSnap.Properties;
@@ -32,6 +33,8 @@ namespace BurageSnap
         public ICommand CaptureCommand { get; private set; }
         public InteractionRequest<IConfirmation> ConfirmationRequest { get; } = new InteractionRequest<IConfirmation>();
         public InteractionRequest<IConfirmation> OptionViewRequest { get; } = new InteractionRequest<IConfirmation>();
+        public ICommand NotifyIconOpenCommand { get; private set; }
+        public ICommand NotifyIconExitCommand { get; private set; }
 
         public bool BurstMode
         {
@@ -52,6 +55,21 @@ namespace BurageSnap
                     : Resources.MainWindow_Start
                 : Resources.MainWindow_Capture;
 
+        public bool ShowInTaskbar => !(WindowState == WindowState.Minimized && Main.Config.ResideInSystemTray);
+
+        public WindowState WindowState
+        {
+            get { return Main.Config.WindowState; }
+            set
+            {
+                if (Main.Config.WindowState == value)
+                    return;
+                Main.Config.WindowState = value;
+                OnPropertyChanged(() => WindowState);
+                OnPropertyChanged(() => ShowInTaskbar);
+            }
+        }
+
         public MainWindowViewModel()
         {
             Main = new Main();
@@ -64,10 +82,16 @@ namespace BurageSnap
                 }
             };
             LoadedCommand = new DelegateCommand(Loaded);
-            ClosingCommand = new DelegateCommand(Closing);
+            ClosingCommand = new DelegateCommand<CancelEventArgs>(Closing);
             BrowseCommand = new DelegateCommand(Main.OpenPictureFolder);
             OptionCommand = new DelegateCommand(SelectOption);
             CaptureCommand = new DelegateCommand(Capture);
+            NotifyIconOpenCommand = new DelegateCommand(() => { WindowState = WindowState.Normal; });
+            NotifyIconExitCommand = new DelegateCommand(() =>
+            {
+                Terminate();
+                Application.Current.Shutdown();
+            });
         }
 
         private void Loaded()
@@ -120,7 +144,26 @@ namespace BurageSnap
             _globelHotKey.Register(Application.Current.MainWindow, config.HotKeyModifier, config.HotKey);
         }
 
-        private void Closing()
+        private void Closing(CancelEventArgs e)
+        {
+            if (Main.Config.ResideInSystemTray)
+            {
+                e.Cancel = true;
+                WindowState = WindowState.Minimized;
+            }
+            else
+            {
+                Terminate();
+            }
+        }
+
+        public void Terminate()
+        {
+            SaveConfig();
+            _globelHotKey.Unregister();
+        }
+
+        private void SaveConfig()
         {
             var config = Main.Config;
             var main = Application.Current.MainWindow;
@@ -128,7 +171,6 @@ namespace BurageSnap
                 ? new Point(main.Left, main.Top)
                 : new Point(main.RestoreBounds.Left, main.RestoreBounds.Top);
             config.Save();
-            _globelHotKey.Unregister();
         }
 
         public static bool IsVisibleOnScreen(Rect rect)
@@ -157,7 +199,7 @@ namespace BurageSnap
 
         private void ConfirmSaveBuffer()
         {
-            Application.Current.MainWindow.WindowState = WindowState.Normal;
+            WindowState = WindowState.Normal;
             ConfirmationRequest.Raise(new Confirmation {Title = Resources.ConfirmView_Title}, c =>
             {
                 if (c.Confirmed)
diff --git a/BurageSnap/NotifyIconWrapper.cs b/BurageSnap/NotifyIconWrapper.cs
new file mode 100644 (file)
index 0000000..3b0ab9e
--- /dev/null
@@ -0,0 +1,106 @@
+// Copyright (C) 2016 Kazuhiro Fujieda <fujieda@users.osdn.me>
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+using System;
+using System.ComponentModel;
+using System.Drawing;
+using System.Reflection;
+using System.Windows;
+using System.Windows.Forms;
+using Application = System.Windows.Application;
+
+namespace BurageSnap
+{
+    public class NotifyIconWrapper : FrameworkElement
+    {
+        private readonly NotifyIcon _notifyIcon;
+
+        public static readonly DependencyProperty TextProperty =
+            DependencyProperty.Register("Text", typeof(string), typeof(NotifyIconWrapper), new PropertyMetadata(
+                (d, e) =>
+                {
+                    if (((NotifyIconWrapper)d)._notifyIcon == null)
+                        return;
+                    ((NotifyIconWrapper)d)._notifyIcon.Text = (string)e.NewValue;
+                }));
+
+        public string Text
+        {
+            get { return (string)GetValue(TextProperty); }
+            set { SetValue(TextProperty, value); }
+        }
+
+        public static readonly RoutedEvent OpenSelectedEvent = EventManager.RegisterRoutedEvent("OpenSelected",
+            RoutingStrategy.Direct, typeof(RoutedEventHandler), typeof(NotifyIconWrapper));
+
+        public event RoutedEventHandler OpenSelected
+        {
+            add { AddHandler(OpenSelectedEvent, value);}
+            remove { RemoveHandler(OpenSelectedEvent, value);}
+        }
+
+        public static readonly RoutedEvent ExitSelectedEvent = EventManager.RegisterRoutedEvent("ExitSelected",
+            RoutingStrategy.Direct, typeof(RoutedEventHandler), typeof(NotifyIconWrapper));
+
+        public event RoutedEventHandler ExitSelected
+        {
+            add { AddHandler(ExitSelectedEvent, value); }
+            remove { RemoveHandler(ExitSelectedEvent, value); }
+        }
+
+        public NotifyIconWrapper()
+        {
+            if (DesignerProperties.GetIsInDesignMode(new DependencyObject()))
+                return;
+            _notifyIcon = new NotifyIcon
+            {
+                // ReSharper disable once AssignNullToNotNullAttribute
+                Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location),
+                Visible = true,
+                ContextMenuStrip = CreateContextMenu()
+            };
+            _notifyIcon.DoubleClick += OpenItemOnClick;
+            Application.Current.Exit += (obj, args) => { _notifyIcon.Dispose(); };
+        }
+
+        private ContextMenuStrip CreateContextMenu()
+        {
+            var contextMenu = new ContextMenuStrip();
+            var openItem = new ToolStripMenuItem(Properties.Resources.NotifyIcon_Open);
+            openItem.Click += OpenItemOnClick;
+            var exitItem = new ToolStripMenuItem(Properties.Resources.NotifyIcon_Exit);
+            exitItem.Click += ExitItemOnClick;
+            contextMenu.Items.Add(openItem);
+            contextMenu.Items.Add(exitItem);
+            return contextMenu;
+        }
+
+        private void OpenItemOnClick(object sender, EventArgs eventArgs)
+        {
+            var args = new RoutedEventArgs(OpenSelectedEvent);
+            RaiseEvent(args);
+        }
+
+        private void ExitItemOnClick(object sender, EventArgs eventArgs)
+        {
+            var args = new RoutedEventArgs(ExitSelectedEvent);
+            RaiseEvent(args);
+        }
+
+        public void Dispose()
+        {
+            _notifyIcon.Dispose();
+        }
+    }
+}
\ No newline at end of file
index b0edd13..e898371 100644 (file)
@@ -20,6 +20,7 @@ namespace BurageSnap
     public class OptionContent
     {
         public bool TopMost { get; set; }
+        public bool ResideInSystemTray { get; set; }
         public int Interval { get; set; }
         public int RingBuffer { get; set; }
         public ObservableCollection<string> WindowTitles { get; set; }
index 2aa331c..a1ff61a 100644 (file)
                     <TextBox x:Name="textBoxRingBuffer" HorizontalAlignment="Left" Text="{Binding Options.RingBuffer}" VerticalAlignment="Center" Width="41" TextAlignment="Right"  Margin="0 0 5 0"/>
                     <Label x:Name="labelFrames" Content="{x:Static proprties:Resources.OptionView_Frames}" HorizontalAlignment="Left" VerticalAlignment="Center"/>
                 </StackPanel>
-                <StackPanel Orientation="Horizontal" Margin="0 0 0 15">
-                    <Label x:Name="labelWindow" Content="{x:Static proprties:Resources.OptionView_Window}" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="0 0 5 0"/>
-                    <CheckBox x:Name="checkBoxTopMost" Content="{x:Static proprties:Resources.OptionView_Top_most}" HorizontalAlignment="Left" VerticalAlignment="Center" IsChecked="{Binding Options.TopMost}"/>
-                </StackPanel>
+                <Grid Margin="0 0 0 15">
+                    <Grid.RowDefinitions>
+                        <RowDefinition/>
+                        <RowDefinition/>
+                    </Grid.RowDefinitions>
+                    <Grid.ColumnDefinitions>
+                        <ColumnDefinition Width="Auto"/>
+                        <ColumnDefinition/>
+                    </Grid.ColumnDefinitions>
+                    <Label x:Name="labelWindow" Content="{x:Static proprties:Resources.OptionView_Window}" Margin="0 0 5 0"/>
+                    <CheckBox x:Name="checkBoxTopMost" Content="{x:Static proprties:Resources.OptionView_Top_most}" Grid.Row="0" Grid.Column="1" Margin="0 0 0 5" IsChecked="{Binding Options.TopMost}"/>
+                    <CheckBox x:Name="checkBoxHideOnMinimize" Content="{x:Static proprties:Resources.OptionView_Reside_in_system_tray}" Grid.Row="1" Grid.Column="1" IsChecked="{Binding Options.ResideInSystemTray}"/>
+                </Grid>
                 <Grid HorizontalAlignment="Left"  VerticalAlignment="Top" Margin="0 0 0 0">
                     <Grid.RowDefinitions>
                         <RowDefinition/>
index 87c8eab..58360f5 100644 (file)
@@ -172,6 +172,24 @@ namespace BurageSnap.Properties {
         }
         
         /// <summary>
+        ///   Exit に類似しているローカライズされた文字列を検索します。
+        /// </summary>
+        public static string NotifyIcon_Exit {
+            get {
+                return ResourceManager.GetString("NotifyIcon_Exit", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Open に類似しているローカライズされた文字列を検索します。
+        /// </summary>
+        public static string NotifyIcon_Open {
+            get {
+                return ResourceManager.GetString("NotifyIcon_Open", resourceCulture);
+            }
+        }
+        
+        /// <summary>
         ///   Add に類似しているローカライズされた文字列を検索します。
         /// </summary>
         public static string OptionView_Add {
@@ -280,6 +298,15 @@ namespace BurageSnap.Properties {
         }
         
         /// <summary>
+        ///   Reside in system tray に類似しているローカライズされた文字列を検索します。
+        /// </summary>
+        public static string OptionView_Reside_in_system_tray {
+            get {
+                return ResourceManager.GetString("OptionView_Reside_in_system_tray", resourceCulture);
+            }
+        }
+        
+        /// <summary>
         ///   Ring buffer: に類似しているローカライズされた文字列を検索します。
         /// </summary>
         public static string OptionView_Ring_buffer {
index db8b668..b4b9f0f 100644 (file)
   <data name="OptionView_Option" xml:space="preserve">
     <value>オプション</value>
   </data>
+  <data name="NotifyIcon_Exit" xml:space="preserve">
+    <value>終了</value>
+  </data>
+  <data name="NotifyIcon_Open" xml:space="preserve">
+    <value>開く</value>
+  </data>
+  <data name="OptionView_Reside_in_system_tray" xml:space="preserve">
+    <value>システムトレイに常駐する</value>
+  </data>
 </root>
\ No newline at end of file
index ae78939..9c8b7e8 100644 (file)
   <data name="OptionView_Option" xml:space="preserve">
     <value>Option</value>
   </data>
+  <data name="NotifyIcon_Open" xml:space="preserve">
+    <value>Open</value>
+  </data>
+  <data name="NotifyIcon_Exit" xml:space="preserve">
+    <value>Exit</value>
+  </data>
+  <data name="OptionView_Reside_in_system_tray" xml:space="preserve">
+    <value>Reside in system tray</value>
+  </data>
 </root>
\ No newline at end of file