海外の組み込みコンパイラの注意点

組込みの開発統合環境としては、ルネサスエレクトロニクスからはCubeSuiteやTexas InstrumentsからはCode Composer Studio、Analog DevicesからはVisual DSP、IARからはEmbedded Work Bench for ARMなどなど、各マイコン・各DSPに対応した統合環境およびコンパイラがあります。この中で海外のコンパイラを使う場合にはちょっと注意が必要です。

多言語対応と言えばUnicodeですが、ソースコードはUnicode保存形式をデフォルトとするのは少ないです。そのようなコンパイラを日本語環境で扱うと使うのはMBCS、具体的にはShift-JISになります。このShift-JISは日本含めアジアの数カ国で使うだけなのでTIやアナデバなどのアメリカから見れば「何それ?」ってなるわけです。それが原因で問題になるソース例はこれ。

Test.c

double AreaOfCircle(double radius)
{
    const double pi = 3.141592;
    const double minRad = 0.1;
    double result;
    if (radius < 0)
    {
        result = minRad * minRad * pi;
        // 負値で返すべきは上位が認識可能な分解能
    }else{
        result = radius * radius * pi;
    }
    return result;
}

このコメント中、’能’はShit-JISでは0x94, 0x5cです。末尾の0x5cはASCIIでいうと ‘¥’ 。C言語的には’¥’はプリプロセサマクロで文字の連結を表します。つまりコメント末尾の文字に ‘¥’があると、その文字がShit-JISと判断できない限り、次の文字列と連結します。そのため上のソースはShift-JIS対応していないコンパイラには以下のように見えます。

Test.c(コンパイラが認識するソース)

double AreaOfCircle(double radius)
{
    const double pi = 3.141592;
    const double minRad = 0.1;
    double result;
    if (radius < 0)
    {
        result = minRad * minRad * pi;
        // 負値で返すべきは上位が認識可能な分解能    }else{
        result = radius * radius * pi;
    }
    return result;
}

よって結果がおかしくなります。そこで組み込み用に書いたソースコードの中で、上記のような問題になる文字が無いかをチェックするツールを用意してみましょう。ルートディレクトリを指定して、それ以下の対象ソースを再帰的に検索し、該当ソースファイルとその行数を出力します。

 Program.cs

namespace ShiftJISFileChecker
{
    using System;
    using System.IO;

    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length != 1)
            {
                ShowHelp(Console.Out);
                return;
            }
            Search0x5cFromFolder(Console.Out, args[0]);
        }

        private static void ShowHelp(TextWriter writer)
        {
            writer.WriteLine(@"使い方:");
            writer.WriteLine(@"コマンドラインからプログラム引数に捜索対象のディレクトリを指定します。");
            writer.WriteLine(@"指定されたフォルダを再帰的に検索し、コメント末尾に0x5Cが含まれている");
            writer.WriteLine("ファイルを出力します。");
            writer.WriteLine();
            writer.WriteLine(@"利用例:SearchJISFileChecker.exe C:\");
            writer.WriteLine(@"出力例:1件見つかりました。");
            writer.WriteLine(@"    C:\work\test.c L.20");
        }

        private static void Search0x5cFromFolder(TextWriter writer, string rootDirectory)
        {
            try
            {
                var searcher = new SearchFolderHierarchcally();
                var result = searcher.Examine(rootDirectory, Search0x5cFromFile.GetFileLines);
                if (result.Count == 0)
                {
                    writer.WriteLine("Shift-JIS問題の懸念となるソースファイルは見つかりませんでした。");
                    return;
                }
                writer.WriteLine("{0}件見つかりました。", result.Count);
                foreach (var item in result)
                {
                    writer.WriteLine(item);
                }
            }
            catch (ArgumentException e)
            {
                writer.WriteLine(e.Message);
            }
        }

    }
}

 

SearchFolderHierarchcally.cs

namespace ShiftJISFileChecker
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.IO;

    internal class SearchFolderHierarchcally
    {
        private List<string> targetExtensions = new List<string>(){
            ".c",
            ".cpp",
            ".h"
        };
        internal IList<string> Examine(string root ,Func<string, IList<string>> examineFunc){
            if (!Directory.Exists(root)){
                throw new ArgumentException("指定されたディレクトリが存在しません");
            }
            var directoryInfo = new DirectoryInfo(root);
            List<string> result = new List<string>();
            foreach(var file in directoryInfo.GetFiles()){
                string fileName = file.FullName;
                if( targetExtensions.Contains(Path.GetExtension(fileName),
                    StringComparer.InvariantCultureIgnoreCase)){
                    // 1ファイルを捜索 + 対象行数を結果にコピー
                    result.AddRange(examineFunc(fileName));
                }
            }
            foreach(var subDirectory in directoryInfo.GetDirectories()){
                // サブディレクトリを再帰的に検索.
                result.AddRange(Examine(subDirectory.FullName, examineFunc));
            }
            return result;
        }
    }
}

 

Search0x5cFromFile.cs

namespace ShiftJISFileChecker
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;

    internal class Search0x5cFromFile
    {
        internal static string filePath;
        private const string targetChars = "―ソЫⅨ噂浬欺圭構蚕十申曾箪貼能表暴予禄兔喀媾彌拿杤歃濬畚秉綵臀藹觸軆鐔饅鷭";

        internal static IList<string> GetFileLines(string targetFilePath)
        {
            filePath = targetFilePath;
            IList<string> result = new List<string>();
            using (StreamReader reader = new StreamReader(filePath, Encoding.GetEncoding("Shift-JIS")))
            {
                ReadAndGet0x5c(reader, result);
            }
            return result;
        }

        private static void ReadAndGet0x5c(StreamReader reader, IList<string> result)
        {
            uint lineCount = 0;
            while (!reader.EndOfStream)
            {
                lineCount++;
                string line = reader.ReadLine();
                if (!line.Equals(String.Empty) &&
                    targetChars.Contains(line[line.Length - 1]))
                {
                    result.Add(filePath + " L:" + lineCount);
                }
            }
        }
    }
}

ざっつおーる。

test

WPFのStaticResourceとDynamicResourceの違い

本来投稿しようと思ってトライしていた内容がずっとうまくいかないので脱線・・。

WPFのリソース参照でStaticResourceとDynamicResourceの違いって理解大丈夫ですか?基本かもしれませんが、時折あれっ;と思うことがありますので抑えときましょう。確認用に簡単なアプリを用意してみます。

 MainWindow.xaml

<Window x:Class="WpfResourceDifference.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="329.785">
    <Window.Resources>
        <SolidColorBrush x:Key="testBrush" Color="Red"/>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
            <RowDefinition Height="30"/>
        </Grid.RowDefinitions>
        <Button Grid.Row="0" Grid.Column="0" Background="{DynamicResource testBrush}"/>
        <Button Grid.Row="1" Grid.Column="0" Background="{StaticResource  testBrush}"/>
        <Grid Grid.Row="2" Grid.Column="0" >
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Button Grid.Row="0" Grid.Column="0" Click="Button_Click_1" >変換①</Button>
            <Button Grid.Row="0" Grid.Column="1" Click="Button_Click_2" >変換②</Button>
            <Button Grid.Row="0" Grid.Column="2" Click="Button_Click_3" >変換③</Button>
        </Grid>
    </Grid>
</Window>

MVVMから外れますがイベントハンドラで処理します、いいんですテストアプリだから。

MainWindow.xaml.cs

namespace WpfResourceDifference
{
    using System.Windows;
    using System.Windows.Media;

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            // 参照オブジェクトのプロパティ変更
            // → StaticResource, DynamicResource両方が影響を受ける
            SolidColorBrush brush = this.FindResource("testBrush") as SolidColorBrush;
            brush.Color = Colors.Blue;
        }

        private void Button_Click_2(object sender, RoutedEventArgs e)
        {
            // リソースオブジェクトの差し替え
            // DynamicResourceのみ影響を受ける
            this.Resources["testBrush"] = new SolidColorBrush(Colors.Green);
        }

        private void Button_Click_3(object sender, RoutedEventArgs e)
        {
            // これじゃなにもかわんないよ。
            var resource = this.Resources["testBrush"] as SolidColorBrush;
            resource = new SolidColorBrush(Colors.Green);
        }
    }
}

コメントに動作書きましたが、起動すると真っ赤なふたつのGridが表示されます。

default

変換①ボタン(イベントハンドラ:Button_Click_1)を押すと以下のようになります。

button_1

さらに変換②ボタン(イベントハンドラ:Button_Click_2)を押すと以下のようになります。

button_2

StaticResource

リソースとバインド先の依存関係プロパティの対応付けは起動時の1回のみ、ただしクラスは参照なのでリソースのプロパティの変更はバインド先も影響を受ける。

DynamicResource

リソースとバインド先の依存関係プロパティの対応付けは起動時および起動中(リソースに変更がある度)。つまりリソースのオブジェクトが変わってもバインド先は影響を受けるし、当然リソースのプロパティ変更はバインド先も影響を受ける。

 

あとおまけですが、WPFのリソースにコードでアクセスする場合2通り方法があります。

  // 1
  this.Resources["testBrush"] = new SolidColorBrush(Colors.Green);
  // 2.
  this.FindResource("testBrush") = new SolidColorBrush(Colors.Green);

前者Resourcesインデクサでは該当リソースをthisクラスの中でのみ行うのに対し、後者FindResourceは街頭リソースをthisクラスで探し、存在しなかった場合上層へ探しに行きます。thisクラスがGridであれば、Grid.ResourcesになければMainWindow.Resources、Application.Resourcesも捜索対象にするということです。前者ではGrid.Resourcesしか探しません。

TFS2013Expressでソースコード管理

個人でソースコード管理する場合Gitを使ったりしていると思いますが、Team Foundation Serverも無料版があるのでこちらも使ってみます。TFSはManagedサービスを公開していたりPowershellスナップインを公開しているのがちょっとしたうま味かも。

1.ダウンロード & インストール

TFS2013Expressをダウンロードしてきてインストールします。インストールはexe形式とiso形式とがありますが、個人でネットワーク接続できる環境ならexeで問題ないでしょう。

install_top

今すぐインストールを実行。コピーが実行され、構成センターが開きます。

3_startconfigurationcenter

ウィザードの開始から順に実行すればOk。場合によってSQLServerのアップデートを要求されますが、WindowsUpdateから推奨されるままにアップデートすればOkです。

6.done

7_confirmこれにてインストールが完了。

 

2.作成 + 接続

VisualStudio2013を立ち上げ、チームエクスプローラタブからチームプロジェクトの作成を行います。

8_connect_9_create

チームプロジェクト名は[プロジェクトコード]、[プロジェクト名]、[物件名] 等を入れます。

10プロジェクトテンプレートを選択して[次へ]を実行するとチームプロジェクトを作成開始するのですが、何度やっても途中で失敗しました。原因は、VisualStudio2013のUpdate2にしていなかったこと、これでアップデートすることで解決しました。

作成できれば同じくチームエクスプローラタブからチームプロジェクトの選択で接続します。

pc名

あとは通常通りプロジェクトを新規作成してチェックイン・チェックアウトをすればOk。

 

C#からExcelを操作する(VSTO)

Excelレガシーという言葉を以前取り上げましたが、私がそれに苦しめられたのは特定のライブラリへの強い依存のためでした。シリアル通信が目的なだけなのにライブラリを使うためにExcel VBAで構築し、挙句管理されていないコードがあちこちに生まれていました。

じゃあExcelの各セルのデータをシリアル送受信したい場合、どうするか?私ならVSTOを使います。そこで今日は簡単にVSTOをご紹介。今もまだExcel VBAにしがみついている人はそれがベストなのか再度自問して頂くきっかけになればと思います。

VSTOはVisualStudioPro以上(確か。ExpressEditionにはありません)で使える機能です。

vsto_project

【Excelアドイン】 と 【Excelブック】 の2種がありますが、これは適用スコープの違いです。

ExcelアドインはExcel全体に適用されます、Excelシートをpdfとして印刷する機能や、今回紹介するExcel上のデータをシリアル送受信する機能などはアドインとして作るのが望ましいでしょう。対して Excelブックは1ブックにのみ適用されます。従来のExcelVBAはこちらに分類されると考えればイイかと思います。今回はExcelアドインを作成します。

あとちなみにVisualStudioのバージョン次第で対象とできるExcelは変わってきます。私の環境にあるExcel2007(古っ!)に対応しているのはVisualStudio2010だけですので今回はこれでいきます。古いExcelを対象にしたアドインであっても基本的に後方(上位)互換性はあるので、新しいExcelでも使えます。新しいExcel固有の機能だけは新しいExcelを対象にできるVisualStudioでないと操作できないみたいです。

 

1.リボンの構成

新しくプロジェクトを作成してリボンを追加します。WindowsForms同様このリボン上にコントロールを配置すれば良いのですが配置できるものはOfficeリボンコントロールに限定されています。

ribbon_control

serial_ribbon

2.機能の実装

リボンにコントロールを配置したらC#でイベントハンドラを作っていくだけです。

namespace ExcelAddInTest
{
    using System;
    using Microsoft.Office.Tools.Ribbon;
    using System.IO.Ports;
    public partial class TestRibbon
    {
        private void TestRibbon_Load(object sender, RibbonUIEventArgs e)
        {
            // ポート名
            var availablePorts = SerialPort.GetPortNames();
            foreach(var port in availablePorts){
                var item = Globals.Factory.GetRibbonFactory().CreateRibbonDropDownItem();
                item.Label = port;
                portDropDown.Items.Add(item);
            }
            // ボーレート
            var availableBaudRates = new string[]{
                "9600",
                "19200",
                "115200",
            };
            foreach (var baud in availableBaudRates)
            {
                var item = Globals.Factory.GetRibbonFactory().CreateRibbonDropDownItem();
                item.Label = baud;
                baudDropDown.Items.Add(item);
            }
            // パリティ
            var parityOptions = new string[]{
                "Even",
                "Odd",
                "None",
            };
            foreach (var parity in parityOptions)
            {
                var item = Globals.Factory.GetRibbonFactory().CreateRibbonDropDownItem();
                item.Label = parity;
                parityDropDown.Items.Add(item);
            }
        }

        private void startCommButton_Click(object sender, RibbonControlEventArgs e)
        {
            var serialPort = new SerialPort(portDropDown.SelectedItem.Label,
                Int32.Parse(baudDropDown.SelectedItem.Label));
            serialPort.Open();

            // T.B.D. serialPortを使ってRead/Write

            serialPort.Close();
        }
    }
}

簡単ですね。1つ注意点として、上で出てきたRibbonDropDownItemはじめVSTOで利用可能な多くはインターフェースになっています。そのためこれを実装したクラスを作る…とも思いがちですが、そこはファクトリーがありますのでそちらを利用します。

これをビルドすると、

VSTO_SerialComm

アドインとして新しいタブが追加され機能が操作できるようになります。

WPF MVVM初心者がみたMVVMライブラリが提供するもの

WPFといえばMVVM。ただWPFのMVVMは補助ライブラリがないと開発がキツイなどと言われます。何がどうキツイのか、ライブラリが何を提供してくれるのかを学習してみました、初心者の私がこれを読む初心者の方にわかるよう説明してみたいと思います。

補助ライブラリとしては、MVVM Light toolkit, Composite Application Guidance for WPF(旧PRISM), Livet が有名です。MVVM Light toolkitは非常に多くダウンロードされているという噂、Composite Application Guidance for WPFはLightToolKitに比べてパフォーマンスが良くないという噂、Livetは国産のMVVMライブラリで細部のサポートが行き届いたライブラリという噂みたいです。

0.ライブラリについて

今回の目的はこれらライブラリが何を目的としているかを探ることなので、どれでもいいのですがひとまずMVVM Light toolkit を使ってみます。ダウンロードしてきてインストールします、何も考えずインストール/Ok連打で問題なし。

install_mvvmtoolkit

VS2010proではダメだったのですが、VS2013proではプロジェクトテンプレートがインストールされるようです。

vs2013_project_mvvmlight

プロジェクトを開くとUnityをDIコンテナとして使っていたりする様子がわかります。多分このテンプレートを使ってサンプルMVVMとして作られたApunta Notasというソフトがあります。ダウンロードしてこのソースコードを読んでみると、app.xamlにViewModelをリソースで登録しておくことで各ウィンドウでそれが参照できるんだなとか、きれいなレイアウトはこうやって作ってるんだな、とか色々学ぶことがあります。

 

1.MVVMのキソ

私が知ったかぶりをして下手な説明をするより、この上なくわかりやすく説明されている方がいらっしゃるのでそちらをご紹介します。とはいえLinkを読まずに本エントリの下記の内容がわかるレベルでMVVMを私なりに説明してみると、、

wpf_mvvm_image

こんな感じかと。注意頂きたいのはWPFはModelに関与せず、ViewModelとViewの部分に関与します。WPF用のMVVMライブラリもViewModelとViewへのサポートがメインです。また、Viewは表示データの管理には携わらないことも重要です。まずこれを頭にいれておいてください。

 

2.ライブラリで提供されるもの

ライブラリを覗くと、Messenger、ビヘイビア、RelayCommandと見慣れないものに気付きます。他にも色々ありますが、これらが今回説明してみようとしているものです。

①ViewModelBase

WPFは華やかなUIという一面ももちますが、MVVMを意識した数多くの機能があります。バインディングもその一つ、以前本ブログで単項データバインディングついて説明しました。INotifyPropertyChangedでデータ更新を通知するんでした。INotifyPropertyChangedにはPropertyChangedイベントがありますが、プロパティのSetterでイベント発砲するのは同じなのでこれを簡略化することが可能です。

Collectionの場合はINotifyCollectionChangedというインターフェースに対してObservableCollectionという クラスがあるのですがINotifyPropertyChangedに対応するものがありませんのでライブラリではこれを提供しています。またViewModelは必ずINotifyPropertyChangedを実装するハズなのでこれを継承したViewModelBaseというものもライブラリが提供します。MVVMで作った場合のViewModelはこのViewModelBaseから派生するのが良いでしょう。

viewmodelの基底

 

②RelayCommand

WPFが規定するのはViewModelとViewの関係であることは上の通りです、基本的にBindingとINotifyPropertyChangedインターフェースによりデータの同期を保ちます。しかし、Viewにはコントロールがありそれを操作したら**するというイベントドリブン的な構造は必要不可欠です。

しかし、イベントハンドラをあちこちに定義すると優れたMVVMからどんどん遠ざかっていきます。DRY違反懸念もありますが、一番気にすべきは責務分離不十分の問題です。ボタンをダブルクリックするなどしてイベントハンドラを定義した場合Window.xaml.csというViewの裏処理であるコードビハインドにロジックを書くことになります。これだとViewが表示以外の責任を持ってしまうのでイベントハンドラでもViewModelに書くべきです。

これを解決するWPF標準の方法はコマンド(ICommandを実装したもの)をViewModelに定義し、それをViewが参照+実行する方法です。

viewmodel_view_command

ただ単純な処理を実行したいのにコマンドを定義するのは面倒なときにRelayCommandがあります。FuncやActionデリゲートをコンストラクタに指定することでICommandインターフェースを実装したViewにバインドできるCommandオブジェクトを作成することができるのです。

『えっ、イベントハンドラをコードビハインドに定義するとMVVMじゃなくなるの?』という質問に対する答えは、厳密にはYES(=MVVMを崩壊させうる)です。そのためにイベントハンドラにコマンドをアタッチするという方法を取ってViewModelに処理をまとめるのがよさそうです。

 

③Messenger, ビヘイビア

ViewModelはViewを参照しません。しかし例えばViewModelは表示データの設定整合なども担うので勝手に調整してよいか、変更をキャンセルするかなどユーザに問い合わせるケースも必要です。しかしViewModelはViewを参照してはならないので、ViewModelからViewへ通知する仕組みが必要です。その一つがMessengerです(他の方法もあります)。

ViewはMessengerによびだされた場合の処理(コールバック、イベントハンドラと同義)を登録しておき、ViewModelが所望のタイミングでMessengerに操作開始を通達することで実現しています。ViewModelからViewを参照する矢印がないことが重要なポイントです。

viewmode_view_messenger

またViewの表示やユーザーインタラクションの部分だけを切り出したものがビヘイビアです。

とまぁ浅い知識かもしれませんが、全体図とだいたい上の内容が頭に入っていると理解しやすいかと思います。MFC時代のDoc-Viewアーキテクチャがどうもしっくりこなかった私にMVVMは強く響く存在になっています。

C#からExcelを操作する(PIAその2)

C#からExcelを操作する方法、前回はWorkSheetの取得までを見てきました。今回はExcel操作の1つ1つについて確認しておきたいと思います。いずれもプロパティの全てを確認することは到底できませんが、関連するクラスを調べていけば所望のプログラムができるかと思います。

ここでは前回のプログラムを引き続き使います。操作FormにWorkSheetオブジェクトは取得できたとして、それらに追加のイベントハンドラを用意する形で個々の操作を見ていきます。見ていく操作は以下になります。

  1. セルにテキストを設定する
  2. セルに数値を設定する
  3. セルに数式を設定する
  4. セルの領域境界に線を引く
  5. チャート(グラフ,表)を挿入する
  6. セルを結合する
  7. セルのフォントを設定する
  8. セルのテキストアライメントを設定する
  9. オートシェイプを挿入する
  10. ハイパーリンクを設定する

 

1.セルにテキストを設定する

セルに文字を設定する方法は前回も出てきましたがこちら。

/// <summary>
/// テキストの設定
/// </summary>
private void setTextButton_Click(object sender, EventArgs e)
{
    xlSheet.Cells[1, 1] = "C#からExcel操作";
    xlSheet.Cells[2, 3] = "GROUP1";
    xlSheet.Cells[2, 4] = "GROUP2";
    xlSheet.Cells[13, 2] = "Sum";
    xlSheet.Cells[15, 2] = "Link";
}

set_text

 

2.セルに数値を設定する

数値を設定する場合もテキストと同じです。

/// <summary>
/// 数値の設定
/// </summary>
private void setNumButton_Click(object sender, EventArgs e)
{
    var random = new Random();
    for (int i = 0; i < 10; i++)
    {
        xlSheet.Cells[i + 3, 2] = i + 1;
        xlSheet.Cells[i + 3, 3] = random.Next(1000);
        xlSheet.Cells[i + 3, 4] = random.Next(1000);
    }
}

set_num

 

3.セルに数式を設定する

数式も同じ。セルの値が変わると自動的に更新されます。

/// <summary>
/// 数式の設定
/// </summary>
private void setFormulaButton_Click(object sender, EventArgs e)
{
    xlSheet.Cells[13, 3] = @"=sum(C3:C12)";
    xlSheet.Cells[13, 4] = @"=sum(D3:D12)";
}

set_formula

 

4.セルの領域境界に線を引く

セルの領域境界に線を引くには範囲を指定してRangeオブジェクトを取得します。

/// <summary>
/// セル境界線の設定
/// </summary>
private void setCellBorderButton_Click(object sender, EventArgs e)
{
    var borderRegion = xlSheet.get_Range("B2", "D13");
    borderRegion.BorderAround();
}

set_range

 

5.チャート(グラフ,表)を挿入する

/// <summary>
/// チャート挿入
/// </summary>
private void insertChartButton_Click(object sender, EventArgs e)
{
    var chart = xlSheet.ChartObjects().Add(250, 20, 300, 250);
    var chartPage = chart.Chart;
    var chartRange = xlSheet.get_Range("C2", "D12");
    chartPage.SetSourceData(chartRange);
    chartPage.ChartType = Excel.XlChartType.xlColumnClustered; 
}

set_chart

 

6.セルを結合する

セルの結合もRangeオブジェクトを取得して行います。

/// <summary>
/// セルのマージ
/// </summary>
private void mergeCellButton_Click(object sender, EventArgs e)
{
    var mergeRange = xlSheet.get_Range("A1", "D1");
    mergeRange.Merge();
}

merge_cell

 

7.セルのフォントを設定する

セルのフォントはセル全体か、セルの一部かで方法が異なります。

/// <summary>
/// フォントの設定
/// </summary>
private void setFontButton_Click(object sender, EventArgs e)
{
    // Cell全体にはFontプロパティ
    xlSheet.Cells[1, 1].Font.Size = 15;
    xlSheet.Cells[1, 1].Font.Bold = true;
    xlSheet.Cells[1, 1].Font.Underline = Excel.XlUnderlineStyle.xlUnderlineStyleDouble;
    xlSheet.Cells[13, 2].Font.OutlineFont = true;

    // Cellの一部にはCharacters
    xlSheet.Cells[1, 1].Characters[1, 2].Font.Color = 255; /* Red */
}

set_font

 

8.セルのテキストアライメントを設定する

/// <summary>
/// アライメントの設定
/// </summary>
private void setAlignmentButton_Click(object sender, EventArgs e)
{
    xlSheet.Cells[1, 1].HorizontalAlignment = Excel.XlHAlign.xlHAlignCenter;
}

set_alignment

 

9.オートシェイプを挿入する

これも前回すこし出てきた内容です。

/// <summary>
/// オートシェイプの挿入
/// </summary>
private void setShapeButton_Click(object sender, EventArgs e)
{
    // 線
    xlSheet.Shapes.AddLine(150, 200, 150, 250);
    // 画像
    xlSheet.Shapes.AddPicture(@"C:\Users\Public\Pictures\Sample Pictures\Lighthouse.jpg",
        Microsoft.Office.Core.MsoTriState.msoTrue,
        Microsoft.Office.Core.MsoTriState.msoTrue,
        100, 250, 100, 100);
}

set_autoshape

 

10.ハイパーリンクを設定する

/// <summary>
/// ハイパーリンクの設定
/// </summary>
private void setHyperLinkButton_Click(object sender, EventArgs e)
{
    xlSheet.Hyperlinks.Add(xlSheet.Cells[15, 2], "https://tocsworld.wordpress.com");
}

set_link

 

ここまで独立して見てきましたが、これらをすべて実行すると以下のようになります。

do_all

今回紹介したのはOfficeのPIAというCOMベースの機能です。Officeには他にもVSTOを使ったものもありますがそちらはまた今度。

C#からExcelを操作する(PIAその1)

個人的な話ですが、ExcelVBAでお粗末なソフトに何度も苦しめられた経験があります。そんな折、Excelレガシーという言葉を知り、激しく共感したところです。Excelを操作するのにVBAだけじゃないんだよ、というメッセージを込めて今日はC#からExcelを操作してみます。

 

1.準備

C#のプロジェクトに参照設定を追加します、COMタブ内 [Microsoft Excel ** Object Library]です。**はバージョンですが、Excel2003ならOffice11、Excel2007ならOffice12、Excel2010ならOffice13を飛ばして14となります。(2003と2007では動作確認しましたが、2010ではできていませんが多分動くはず)

add_re_excel

 

2.起動しているExcelを操作する

まずはExcelが開いており、それに対してデータを書き込む想定です。説明上、1つ1つの操作をわかりやすくする目的でWindowsFormsアプリとしてイベントハンドラに処理を分けて記載してみます。

excel_operate_form

 ExcelOperateForm.cs

namespace ExcelOperation
{
    using System;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;
    using Excel = Microsoft.Office.Interop.Excel;

    public partial class ExcelOperateForm : Form
    {
        Excel._Application xlApp;
        Excel.Workbooks xlBooks;
        Excel.Workbook xlBook;
        Excel.Worksheet xlSheet;

        public ExcelOperateForm()
        {
            InitializeComponent();
        }

        private void getOpeningExcelButton_Click(object sender, EventArgs e)
        {
            // 開いているExcelを取得
            try
            {
                xlApp = (Excel.Application)Marshal.GetActiveObject("Excel.Application");
            }
            catch (COMException ex)
            {
                MessageBox.Show("Excelが起動されていません" + ex.Message);
                return;
            }

            // 開いているExcelのBookを取得
            // <MEMO>
            // Excel操作ソフトを実行中に強制終了などするとCOMオブジェクトの解放モレが発生し、
            // プロセスが残る状態になる。その状態で再度Excel操作をしようとすると、残った
            // プロセスの方を操作対象としてしまう。結果、新しいブックを開いていても以下の操作に
            // 失敗することがある。この場合、タスクマネージャーからEXCEL.EXEプロセスをキルする必要がある。
            xlBooks = xlApp.Workbooks;
            if (xlBooks.Count == 0)
            {
                MessageBox.Show("プロセスが残っていないか確認してください");
                return;
            }
            xlBook = xlBooks.get_Item(1);
            xlSheet = (Excel.Worksheet)xlBook.Sheets.get_Item(1);
        }

        private void setTextButton_Click(object sender, EventArgs e)
        {
            if (xlSheet == null)
            {
                return;
            }
            xlSheet.Cells[1, 1] = "a";
        }

        private void setNumberButton_Click(object sender, EventArgs e)
        {
            if (xlSheet == null)
            {
                return;
            }
            xlSheet.Cells[2, 1] = "10";
        }

        private void setFormulaButton_Click(object sender, EventArgs e)
        {
            if (xlSheet == null)
            {
                return;
            }
            xlSheet.Cells[2, 2] = "=A2*10";
        }

        private void setLineButton_Click(object sender, EventArgs e)
        {
            if (xlSheet == null)
            {
                return;
            }
            xlSheet.Shapes.AddLine(1, 1, 100, 100);
        }
    }
}

注意点としては複数のExcelが起動している場合、それを識別することはできません。プロセス一覧を取得して対応付けて…で可能かもしれませんが茨の道です。そもそもCOMのオートメ-ションなんかにそんな引数もなかったように思うし、そもそもムリかもしれません。

上記コードOffice2003, 2007で動作確認しましたが、一度トラブりました。VisualStudioから実行していて、GetActiveObjectでExcel.Aplicationが取得できないのです。32bit ⇔ 64bitの違いはない筈だし・・・等と考えましたが、答えはStackOverFlow先生にありました。原因はVisualStudioを管理者として実行していたためです。これは気付きづらいですね;

各コマンドを実行したExcelの様子はこちら。

run_excel

 

3.Excelファイルを新規作成して操作する

測定データのロギングなどでは上の方法で良いかとも思いますが、Excelファイルを作成したい場合はちょっとやり方が異なります。こちらも同じ要領でWindowsFormsアプリにしてみます。

excel_create_form

 ExcelCreateForm.cs

namespace ExcelOperation
{
    using System;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;
    using Excel = Microsoft.Office.Interop.Excel;

    public partial class ExcelCreateForm : Form
    {
        Excel._Application xlApp;
        Excel.Workbooks xlBooks;
        Excel.Workbook xlBook;
        Excel.Worksheet xlSheet;

        public ExcelCreateForm()
        {
            InitializeComponent();
        }

        private void createExcelFileButton_Click(object sender, EventArgs e)
        {
            xlApp = new Excel.Application();
            xlBook = xlApp.Workbooks.Add();
            xlSheet = (Excel.Worksheet)xlBook.Sheets.get_Item(1);
        }

        private void setTextButton_Click(object sender, EventArgs e)
        {
            if (xlSheet == null)
            {
                return;
            }
            xlSheet.Cells[1, 1] = "a";
        }

        private void setNumberButton_Click(object sender, EventArgs e)
        {
            if (xlSheet == null)
            {
                return;
            }
            xlSheet.Cells[2, 1] = "10";
        }

        private void setFormulaButton_Click(object sender, EventArgs e)
        {
            if (xlSheet == null)
            {
                return;
            }
            xlSheet.Cells[2, 2] = "=A2*10";
        }

        private void setLineButton_Click(object sender, EventArgs e)
        {
            if (xlSheet == null)
            {
                return;
            }
            xlSheet.Shapes.AddLine(1, 1, 100, 100);
        }

        private void ExcelCreateForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            xlBook.Close();
            xlApp.Quit();
            Marshal.ReleaseComObject(xlApp);
            xlApp = null;
        }
    }
}

これを実行して各ボタンを押下した後Formを閉じようとすると以下のようになります。

save_excel

この辺りはユーザ問い合わせなくファイル保存するとかできますので適宜ケアしてください。ちなみに内部オブジェクトとしてExcelを処理する方法では管理者権限でも問題なく動作します。動作結果は先ほどと同じなので省略。

Excelの操作は他にも色々あるのでExcelVBAを唯一神としないようご注意ください。