気楽なソフト工房

プログラミングについていろいろな記事を書いています。



mykonos2008

Author:mykonos2008
システムエンジニアとして働いている30代の会社員です。
仕事や趣味でプログラムを書いている方の役に立つ記事を書いていきたいと思っています。
ご意見、ご感想はこちらまで
If you are an english speaker,Please visit my english blog.

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
SilverlightでScrollViewerを使用すると「おやっ」と思うことがあります。
それは、「マウスホイール」によるスクロールが効かないことです。

でも安心してください。ちゃんと対応されています。
XAMLに以下のように記述するだけで、効くようになります。

<ScrollViewer Background="Transparent">

Silverlightでは、Brushを指定していないコントロールに対しては、hittestが有効にならない挙動があるみたいで、
透明なBrushを設定することで、回避できます。

スポンサーサイト
電光掲示板のように文字列を左から右にスクロールさせるサンプルを作ってみました。


Silverlightではアニメーションを簡単に実現できる仕組みが提供されているので、とても楽に出来ました。

まず、XAML側に、Canvasを定義し、その中にスクロールさせる文字列をTextBlockで配置します。
そしてResourceとしてStoryBoardを定義します。そして「DoubleAnimation」を使って、TextBlockの「Canvas.Left」プロパティを
変化させます。

DoubleAnimationのFromやToは、HTMLに指定されるSilverlightのサイズに、動的に合わせて変化するようにしたかったので
コード上で指定するようにしました。

以下がXAMLの内容です。

<UserControl x:Class="BulletinBoard.BoardControl"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Loaded="UserControl_Loaded">
    <UserControl.Resources>
        <Storyboard x:Name="_storyBoard">
            <DoubleAnimation x:Name="_animation" RepeatBehavior="Forever" Storyboard.TargetName="_boardText"
               Storyboard.TargetProperty="(Canvas.Left)" Duration="0:0:10"/>
        </Storyboard>
    </UserControl.Resources>
    <Canvas x:Name="LayoutRoot" Background="Gray">
        <TextBlock Foreground="White" FontSize="15" x:Name="_boardText">
          Silverlightで文字列を横にスクロールさせるサンプルです。
        </TextBlock>
    </Canvas>
</UserControl>

次にコードの方からDoubleAnimationのFromをSilverlight領域の幅の値に、
Toを0からTextBlockの幅をマイナスした値に設定しました。

ActualWidthとActualHeightを取得する必要があったので、
DispactherのBeginInvokeを使用して、レイアウトシステムの処理の後に取得処理が
走るようにしました。

    private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {
        Dispatcher.BeginInvoke(() =>
        {
            //文字の縦表示位置を設定する
            Canvas.SetTop(_boardText, (this.ActualHeight - _boardText.ActualHeight) / 2);

            //文字がすべて隠れるToの位置を設定する
            _animation.To = - _boardText.ActualWidth;

            //アニメーションのFromをコントロールの幅に設定する
            _animation.From = this.ActualWidth;

            //アニメーションを開始する
            _storyBoard.Begin();
        });
    }

文字列や各種設定を外部から与えられるようにもう少し作りこんでみようと思います。

さて、いよいよ、「Silverlight4」のリリースが近づいてきましたね、楽しみです。
そこで、本日は「Silverlight4」のComオートメーションのちょっとしたTipsをご紹介させていただきます。

Comオートメーションをアプリから利用する際(Silverlightに限らず)、一番頭を悩ませるのは
一度取得したComオブジェクトのリソース解放です。使用したComオブジェクトを1つでも、
解放し忘れると、Comオブジェクトのプロセスが残ってしまいます。

「Silverlight4」ではComオブジェクトは、「IDisposable」インターフェースを実装しているので、
「using句」を利用したり、直接Dispose()をコールすることで、リソースを解放することが出来ます。

ただ、問題はほとんどの場合、解放しなければいけないオブジェクトが1つで済まないことです。
例えばExcelをComオートメーションで利用して、新規のブックに、データを書き込んで保存処理を
行うとします。
この場合、以下のようなオブジェクトが必要になります。

・Application(Excel)
・Workbooks
・Workbook
・Worksheets
・Worhsheet
・Range
・Font
・Interior

Rangeなんかは入れ子になったりするので、実際はもっと多いです。
これらを「using」句を使って解放するとなると、、、

   using(dynamic excel = AutomationFactory.CreateObject("Excel.Application")){
      using(dynamic workbooks = excel.WorkBooks){
         using(dynamic workbookObject = workbooks.Add()){
            /////////どんどん入れ子に。。。
         }
      }
   }

ちょっときつい。。ですね。。

そこでちょっとしたライブラリを考えてみました。
アプリケーションで、利用するComオブジェクトをラップするクラスを作り、
IDisposableインターフェースを実装します。

例えばWorkbooksのラッパーはこんな感じです。
    public class Workbooks : IDisposable
    {
        //Comオブジェクト
        private dynamic _workbooks;

        internal Workbooks(dynamic workbooks)
        {
            _workbooks = workbooks;
        }

///省略
    }

そして、新規ブック追加など新たなComオブジェクトへの参照が生成される処理で、
以下のようにして、新規生成されたオブジェクトを管理コレクションに登録しておきます。

        public Workbook CreateNewBook()
        {
            dynamic workbookObject = _workbooks.Add();

            //ブックのラッパー
            Workbook workbook = new Workbook(workbookObject);

            //管理コレクションに登録する処理
            AddChildren(workbook);

            return workbook;
        }

そして、Dispose()メソッドの中で、ラップしているComオブジェクトと、管理コレクションに登録されている
派生オブジェクトを解放する処理を記述します。

        public void Dispose()
        {
            try
            {
                //子オブジェクトを解放する
                ReleaseChildren();

                ((IDisposable)_workbooks).Dispose();

            }
            catch { }
        }

こうすることで何がよいかと言うと利用する側は以下のようなコードで済みます。

    using(Workbooks books = excel.Workbooks){
        //いろんな処理
    }

外側のWorkbooksがそれから派生する他のComオブジェクトを管理し、またそれらのラッパークラスが
さらにその子供を管理し、それぞれリソース解放を担ってくれるので、利用する側は、自分が解放したい単位で、
外側のオブジェクト(今回はWorkbooks)を決定し、それのみ解放処理を記述すればよいのです。

実際は、「SilverOffice」という名前のもう少し手の込んだサンプルライブラリを作成してみました。
ただ、オブジェクトやプロパティは全然そろえていないので、考え方が使えそうであれば、
必要なオブジェクトやプロパティを追加して使用してみてください。

SilverOfficeのダウンロード

いよいよ、「Silverlight 4」のリリースが近づいてきました。私も待ちきれず、VisualStudioのRC版でいろいろ試してみています。
「Silverlight 4」の新機能の中でも、私が最も注目しているのはComオートメーションの機能です。

ExcelとComオートメーション機能を使って、いろいろ試してみているのですが、なかなか快適です。
Silverlightでは遅延バインディングしか対応していないのですが、C# 4.0から導入された「dynamic」キーワードを
使用できるのでかなり楽にきれいなコードを書くことが出来ます。

例えば、Excelを起動してブックを開くコードは以下のようにして記述することが出来ます。

//Excelオブジェクトを生成する
dynamic excelObject = AutomationFactory.CreateObject("Excel.Application");

//Excelを可視化する
excelObject.Visible = true;
excelObject.UserControl = true;

//ブックを開く
dynamic workbooks = excelObject.WorkBooks;
dynamic workbookObject = workbooks.Open("ファイルパス");

以前のC#だとリフレクションの機能を利用して面倒くさいコードを書かないといけなかったので、
かなり楽になりました。

※ComオブジェクトはIDisposableインターフェースを実装しているので使用したオブジェクトは
すべて、Dispose()することをお忘れなく。

ところで、Excel2003とXP、またはVistaの組み合わせだと、Workbookの「Save」、または
「SaveAs」を使用すると、オブジェクトを破棄した後もExcelのプロセスが残ってしまうと
いう衝撃的な問題があるようです。解決策をご存知の方は是非教えてください。

今のところ、WMIをComオートメーションを介して使用して、プロセスをKILLするくらいしか
解決策が思いつきません。。。。


本日のTipsでは、先日、公開させて頂いた「Crete For Web」のコードを使ってDataGridの基本的な使用方法を紹介していきます。

DataGridにデータを表示する
「Crete For Web」ではホームページのキーワード解析を行った結果をDataGridに表示しています。
DataGridに表示している項目は[単語]、[単語の出現回数]、[単語の出現率]、[単語の出現するタグ]の4つ。



まずはXAMLにDataGridのタグを記述します。

[Page.xaml]
<UserControl x:Class="CreteWeb.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
    Width="700">

   //省略

   <data:DataGrid x:Name="_wordList" Height="250" Width="580" IsReadOnly="True"
      VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" ></data:DataGrid>
</UserControl>

「System.Windows.Controls.Data.Dll」に参照を追加し、名前空間の記述を追加します。

そして、コードから参照できるように「x:Name」属性を設定しています。
また、スクロール可能なように、VerticalScrollBarVisibility属性とHorizontalScrollBarVisibility属性をAutoに設定しています。

続いて、コードを見ていきます。

まず、解析結果を管理するためのエンティティクラスを作成します。

    public class Keyword
    {
        //単語
        public String Word { get; set; }
        //単語の出現回数
        public String Count { get; set; }
        //単語の出現率
        public String Ratio { get; set; }
        //単語の出現するタグ
        public String Tags { get; set; }
    }

次にDataGridに設定する部分です。

[Page.xaml.cs]

  //複数のキーワードを管理するコレクション
  List keywords = new List();

  //形態素解析のAPIから取得したXMLのノードをループする
  foreach (var node in query)
  {
     Keyword keyword = new Keyword();

     //keywordの各プロパティに値を設定するコード(省略)

     keywords.Add(keyword);
  }

  //DataGridにKeywordのコレクションを設定する
  _wordList.ItemsSource = keywords;


ここまで、とりあえずDataGridにデータを表示することが出来ます。
DataGridのItemsSourceに指定できるオブジェクトは「foreach」可能なオブジェクトです。

列名を変更する
ここまでのコードでは、DataGridの列名には、Keywordクラスのプロパティ名がそのまま
表示されています。これを分かりやすい日本語に変更します。

XAMLのDataGridを配置している部分を以下のように変えます。

[Page.xaml]
<data:DataGrid x:Name="_wordList" Height="250" Width="580" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto"
          IsReadOnly="True" AutoGenerateColumns="False">
    <data:DataGrid.Columns>
        <data:DataGridTextColumn Header="キーワード" Binding="{Binding Word}" />
        <data:DataGridTextColumn Header="出現回数" Binding="{Binding Count}" />
        <data:DataGridTextColumn Header="割合(%)" Binding="{Binding Ratio}" />
        <data:DataGridTextColumn Header="タグ" Binding="{Binding Tags}" />
    </data:DataGrid.Columns>
</data:DataGrid>

まず、DataGridのAutoGenerateColumns属性をFalseに設定します。これをやらないと同じ列が重複して出来てしまいます。
DataGridTextColumnのBinding属性には、それぞれの列に対応するKeywordクラスのプロパティ名を指定しています。
その他、Width属性を指定すると列の幅を設定することも出来ます。

選択行のオブジェクトを取得する
「Crete For Web」ではキーワードの一覧表(上記DataGrid)から項目を選択し、関連ワードボタンをクリックすると
選択された単語と一緒によく検索される関連語の一覧を取得することが出来ます。

この際、現在DataGridで選択されている行のKeywordクラスのインスタンスを、以下のようにして取得しています。

[Page.xaml.cs]
   Keyword keyword = _wordList.SelectedItems[0] as Keyword;

行の色を変更する
関連語一覧では、解析対象のページに含まれているキーワードの組み合わせについては、
青色で、その他の場合は、黒色で表示しています。

この処理は、DataGridのLoadingRowイベントを利用して行っています。

[Page.xaml.cs]
  public Page()
  {
      InitializeComponent();

       //イベントハンドラを追加
      _relatedList.LoadingRow += new EventHandler(_relatedList_LoadingRow);
  }

  private void _relatedList_LoadingRow(object sender, DataGridRowEventArgs e)
  {
      RelatedWord relatedWord = e.Row.DataContext as RelatedWord;

       //判定処理(省略)

      if (//ふくまれている)
      {
          e.Row.Foreground = new SolidColorBrush(Colors.Blue);
      }
      else
      {
          e.Row.Foreground = new SolidColorBrush(Colors.Black);
      }
   }

LoadingRowイベントはDataGridRow がインスタンス化され、画面に表示される前に発生します。
両方のケースについてしっかりと色指定を行わないと、条件に合わない行まで青色で表示されたりするので、
ご注意ください。

余談ですが、DataGridはItemsSourceにデータが設定された段階で全ての行をインスタンス化するのではなく、
行が必要になった時点で、インスタンス化を行います。この仕様のおかげで画面表示がすばやく行われます。
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。