HeyCHのブログ

慢性疲労のへいちゃんです

【C#】デスクトップメモを作ってみよう その2

今回の記事は前回の記事の続きとして書いていきます。
heych.hatenablog.com


今回は機能設定できるようにした時計のフォントの変更、色の変更を保存できるようにしたいと思います。

シリアライズ/デシリアライズ

C#にはシリアライズ/デシリアライズという機能があり、それでクラスの内容を保存/読み込みができるようになっています。
今回は読み込み時に問題が発生したため、読み込みは手動で行っていますが、基本的にはXmlSerializerの機能でやっています。
ちょっとスマートではありませんが、以下のようにする事でWindowの位置、フォント、色を記憶することができます。

<Window x:Class="DesktopMemo.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:DesktopMemo"
        mc:Ignorable="d"
        Title="MainWindow" Height="120" Width="300" Left="{Binding MainLeft,Mode=TwoWay}" Top="{Binding MainTop,Mode=TwoWay}"
        SizeToContent ="WidthAndHeight"
        WindowStyle="None"
        AllowsTransparency="True"
        Background="Transparent">
    <Grid>
        <TextBlock x:Name="label1" Text="{Binding Time}" FontSize="36" FontWeight="Bold">
            <TextBlock.Resources>
                <Style TargetType="TextBlock">
                    <Setter Property="Cursor" Value="Hand"/>
                </Style>
            </TextBlock.Resources>
        </TextBlock>
    </Grid>
    <Window.ContextMenu>
        <ContextMenu Name="_menu" StaysOpen="true">
            <MenuItem Header="フォント" Click="MenuItem_Click_1" />
            <MenuItem Header="カラー" Click="MenuItem_Click_2" />
            <Separator />
            <MenuItem Header="終了" Click="MenuItem_Click" />
        </ContextMenu>
    </Window.ContextMenu>
</Window>
  • WindowのLeftとTopの値をバインディングする場合はMode=TwoWayにしないとだめ
  • 「SizeToContent ="WidthAndHeight"」を用いることでウィンドウをコンテンツに合わせた大きさにすることができる
using System;
using System.Collections.Generic;
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 System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Forms;
using System.Xml.Serialization;
using System.IO;
using System.Threading;
using System.Xml;

namespace DesktopMemo {
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window {
        ViewModel vm = new ViewModel();
        public MainWindow() {
            InitializeComponent();
            vm.MainWindow = this;
            this.DataContext = vm;

            //デシリアライズするとなぜかMainWIndowがnullになってしまう。原因不明
            if (File.Exists(@"data.xml")) {
                XmlDocument doc = new XmlDocument();
                doc.Load(@"data.xml");
                var MainLeft = doc.SelectSingleNode("//MainLeft");
                var MainTop = doc.SelectSingleNode("//MainTop");
                var MenuFontAsString = doc.SelectSingleNode("//MenuFontAsString");
                var MainColorAsString = doc.SelectSingleNode("//MainColorAsString");
                vm.MainLeft = double.Parse(MainLeft.InnerText);
                vm.MainTop = double.Parse(MainTop.InnerText);
                vm.MenuFontAsString = MenuFontAsString.InnerText;
                vm.MainColorAsString = MainColorAsString.InnerText;
            }

            BackgroundWorker bw = new BackgroundWorker();
            bw.DoWork += Bw_DoWork;
            bw.RunWorkerAsync();
        }

        private void Bw_DoWork(object sender, DoWorkEventArgs e) {
            while (true) {
                vm.OnPropertyChanged("Time");
                System.Threading.Thread.Sleep(100);
            }
        }
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
            base.OnMouseLeftButtonDown(e);

            try {
                DragMove();
            } catch { }
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e) {
            //終了する時にシリアライズして保存
            XmlSerializer xs = new XmlSerializer(typeof(ViewModel));
            using (var sw = new StreamWriter(@"data.xml", false, Encoding.UTF8)) {
                xs.Serialize(sw, vm);
            }
            this.Close();
        }

        private void MenuItem_Click_1(object sender, RoutedEventArgs e) {
            FontDialog fd = new FontDialog();
            if (fd.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
                vm.MainFont = fd.Font;
            }
        }

        private void MenuItem_Click_2(object sender, RoutedEventArgs e) {
            ColorDialog cd = new ColorDialog();
            if (cd.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
                vm.MainColor = cd.Color;
            }
        }
    }

    public class ViewModel : INotifyPropertyChanged {
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string propertyName) {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        MainWindow _MainWindow = null;
        [XmlIgnore]
        internal MainWindow MainWindow {
            get { return _MainWindow; }
            set { _MainWindow = value; }
        }
        public string Time {
            get {
                return DateTime.Now.ToString("HH:mm:ss");
            }
        }
        double _MainLeft = 0;
        public double MainLeft {
            get {
                return _MainLeft;
            }
            set {
                _MainLeft = value;
                OnPropertyChanged("MainLeft");
            }
        }
        double _MainTop = 0;
        public double MainTop {
            get {
                return _MainTop;
            }
            set {
                _MainTop = value;
                OnPropertyChanged("MainTop");
            }
        }
        System.Drawing.Font _MainFont = System.Drawing.SystemFonts.DefaultFont;
        [XmlIgnore]
        public System.Drawing.Font MainFont {
            get {
                return _MainFont;
            }
            set {
                _MainFont = value;
                OnPropertyChanged("MainFont");
                //label1のフォント変更
                MainWindow.label1.FontFamily = new FontFamily(_MainFont.Name);
                MainWindow.label1.FontSize = _MainFont.Size * 96.0 / 72.0;
                MainWindow.label1.FontWeight = _MainFont.Bold ? FontWeights.Bold : FontWeights.Regular;
                MainWindow.label1.FontStyle = _MainFont.Italic ? FontStyles.Italic : FontStyles.Normal;
                TextDecorationCollection tdc = new TextDecorationCollection();
                if (_MainFont.Underline) tdc.Add(TextDecorations.Underline);
                if (_MainFont.Strikeout) tdc.Add(TextDecorations.Strikethrough);
                MainWindow.label1.TextDecorations = tdc;
            }
        }
        public string MenuFontAsString {
            get { return ConvertToString(MainFont); }
            set { MainFont = ConvertFromString<System.Drawing.Font>(value); }
        }

        System.Drawing.Color _MainColor = System.Drawing.Color.Black;
        [XmlIgnore]
        public System.Drawing.Color MainColor {
            get {
                return _MainColor;
            }
            set {
                _MainColor = value;
                OnPropertyChanged("MainColor");
                //label1のカラー変更
                MainWindow.label1.Foreground
                    = new SolidColorBrush(System.Windows.Media.Color.FromRgb(_MainColor.R, _MainColor.G, _MainColor.B));
            }
        }
        public string MainColorAsString {
            get { return ConvertToString(MainColor); }
            set { MainColor = ConvertFromString<System.Drawing.Color>(value); }
        }

        //FontやColorを文字列にしてくれる奴
        public static string ConvertToString<T>(T value) {
            return TypeDescriptor.GetConverter(typeof(T)).ConvertToString(value);
        }
        public static T ConvertFromString<T>(string value) {
            return (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(value);
        }
    }
}
  • XmlSerializer.Deserializeを使うとViewModelのMainWindowがnullになってデシリアライズできない
  • FontやColorをシリアライズするため「ConvertToString」「ConvertFromString」関数を使用する。
  • シリアライズするとViewModel内のMainWindowがnullになってしまう原因は、デシリアライズする際、空のコンストラクタが呼び出されることにより、初期化されるためだと考えられる。(20200506追記)
using System;
using System.Collections.Generic;
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 System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Forms;
using System.Xml.Serialization;
using System.IO;
using System.Threading;
using System.Xml;

namespace DesktopMemo {
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window {
        ViewModel vm = new ViewModel();
        public MainWindow() {
            InitializeComponent();
            this.DataContext = vm;

            //デシリアライズするとなぜかMainWIndowがnullになってしまう。
            //原因でデシリアライズの際空のコンストラクタが呼ばれ、MainWindow=nullになってしまうから。
            if (File.Exists(@"data.xml")) {
                XmlDocument doc = new XmlDocument();
                doc.Load(@"data.xml");
                var MainLeft = doc.SelectSingleNode("//MainLeft");
                var MainTop = doc.SelectSingleNode("//MainTop");
                var MenuFontAsString = doc.SelectSingleNode("//MenuFontAsString");
                var MainColorAsString = doc.SelectSingleNode("//MainColorAsString");
                vm.MainLeft = double.Parse(MainLeft.InnerText);
                vm.MainTop = double.Parse(MainTop.InnerText);
                vm.MenuFontAsString = MenuFontAsString.InnerText;
                vm.MainColorAsString = MainColorAsString.InnerText;

                ChangeFont(vm.MainFont, textBlock1);
                ChangeColor(vm.MainColor, textBlock1);
            }

            BackgroundWorker bw = new BackgroundWorker();
            bw.DoWork += Bw_DoWork;
            bw.RunWorkerAsync();
        }

        private void Bw_DoWork(object sender, DoWorkEventArgs e) {
            while (true) {
                vm.OnPropertyChanged("Time");
                System.Threading.Thread.Sleep(100);
            }
        }
        /// <summary>
        /// label1のフォント変更
        /// </summary>
        private void ChangeFont(System.Drawing.Font font, TextBlock tage) {
            tage.FontFamily = new FontFamily(font.Name);
            tage.FontSize = font.Size * 96.0 / 72.0;
            tage.FontWeight = font.Bold ? FontWeights.Bold : FontWeights.Regular;
            tage.FontStyle = font.Italic ? FontStyles.Italic : FontStyles.Normal;
            TextDecorationCollection tdc = new TextDecorationCollection();
            if (font.Underline) tdc.Add(TextDecorations.Underline);
            if (font.Strikeout) tdc.Add(TextDecorations.Strikethrough);
            tage.TextDecorations = tdc;
        }
        /// <summary>
        /// label1のカラー変更
        /// </summary>
        private void ChangeColor(System.Drawing.Color color, TextBlock tage) {
            tage.Foreground
                = new SolidColorBrush(System.Windows.Media.Color.FromRgb(color.R, color.G, color.B));
        }
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) {
            base.OnMouseLeftButtonDown(e);

            try {
                DragMove();
            } catch { }
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e) {
            //終了する時にシリアライズして保存
            XmlSerializer xs = new XmlSerializer(typeof(ViewModel));
            using (var sw = new StreamWriter(@"data.xml", false, Encoding.UTF8)) {
                xs.Serialize(sw, vm);
            }
            this.Close();
        }

        private void MenuItem_Click_1(object sender, RoutedEventArgs e) {
            FontDialog fd = new FontDialog();
            if (fd.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
                vm.MainFont = fd.Font;
                ChangeFont(vm.MainFont, textBlock1);
            }
        }

        private void MenuItem_Click_2(object sender, RoutedEventArgs e) {
            ColorDialog cd = new ColorDialog();
            if (cd.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
                vm.MainColor = cd.Color;
                ChangeColor(vm.MainColor, textBlock1);
            }
        }
    }

    public class ViewModel : INotifyPropertyChanged {
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string propertyName) {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        public string Time {
            get {
                return DateTime.Now.ToString("HH:mm:ss");
            }
        }
        double _MainLeft = 0;
        public double MainLeft {
            get {
                return _MainLeft;
            }
            set {
                _MainLeft = value;
                OnPropertyChanged("MainLeft");
            }
        }
        double _MainTop = 0;
        public double MainTop {
            get {
                return _MainTop;
            }
            set {
                _MainTop = value;
                OnPropertyChanged("MainTop");
            }
        }
        System.Drawing.Font _MainFont = System.Drawing.SystemFonts.DefaultFont;
        [XmlIgnore]
        public System.Drawing.Font MainFont {
            get {
                return _MainFont;
            }
            set {
                _MainFont = value;
                OnPropertyChanged("MainFont");
            }
        }
        public string MenuFontAsString {
            get { return ConvertToString(MainFont); }
            set { MainFont = ConvertFromString<System.Drawing.Font>(value); }
        }

        System.Drawing.Color _MainColor = System.Drawing.Color.Black;
        [XmlIgnore]
        public System.Drawing.Color MainColor {
            get {
                return _MainColor;
            }
            set {
                _MainColor = value;
                OnPropertyChanged("MainColor");
            }
        }
        public string MainColorAsString {
            get { return ConvertToString(MainColor); }
            set { MainColor = ConvertFromString<System.Drawing.Color>(value); }
        }

        //FontやColorを文字列にしてくれる奴
        public static string ConvertToString<T>(T value) {
            return TypeDescriptor.GetConverter(typeof(T)).ConvertToString(value);
        }
        public static T ConvertFromString<T>(string value) {
            return (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(value);
        }
    }
}
  • ViewModelでViewを変更するのはどうかと思い変更したコードです。

f:id:HeyCH:20200506014004p:plain