ラベル TaskManager の投稿を表示しています。 すべての投稿を表示
ラベル TaskManager の投稿を表示しています。 すべての投稿を表示

ガントチャート作成日記

あいかわらずのんびりと作成中。
タスクの削除を付けたが、チャートの削除をまだつけていないことに気づいた。
っていうかそれ以前にデータの保存処理を付けてないし、初期の日付範囲とかも一切つけていない。



プロジェクトの初期設定として、プロジェクトの開始日・終了日を変更できるようにすることから始めるべきかな・・・。

頑張ったところとしては、左側のタスクの一番左側のタスクの縦幅を広げて、大きいグループの範囲を広げた時に、濃い線をタスクの一覧とガントチャートの一覧に表示して、どこまでがグループに属しているかをわかりやすくしたことと、マウスで複数のチャートを選択・移動できるようにした。

グループを表現する線についてはGanttGridTemplateSelecter.csとGanttGridTaskViewTemplateSelecter.csにパターンを追加して、DataTemplateを新しく定義してこちゃこちゃすればできるけど、マウスでの移動については一苦労した。
CanvasコントロールをGridコントロールの下に張り付けて、Rectを新たに描画している。

Canvasコントロールを追加したことでスクロールバーがうまく出なくなってしまったので、SizeChangeイベントで変更するようにした。

マウスの範囲選択でGridコントロール上のチャートを選択するのもネットで調べてみるといろいろとサンプルがある。
世の中にはすごい人がいるなぁ。

<ソース>
        #region 右側のCanvasのサイズを合わせる

        // マウスで複数選択をさせるためにCanvasコントロールを利用しているがCanvasコントロールはScrollBarが出しずらい
        // ガントチャートの上にCanvasをTransportで重ねることも考えたが、チャートをクリックできなくなってしまう。
        // Canvasコントロールをクリックしたときの通知などをチャート側に伝えるようにすればよいが、さすがに手間なので
        // Canvasコントロールのサイズを変更しScrollBarが出るようにした。

        /// <summary>
        /// 幅を合わせる
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void gridCalender_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            Grid grid = (Grid)sender;
            CalenderBaseCanvas.Width = e.NewSize.Width;
        }

        /// <summary>
        /// 高さはタスク一覧側と同じになるので合わせる
        /// gridCalender側と合わせるとタスクが少ないときにスクロールバーが上に出てしまう
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void icTaskGrid_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            CalenderBaseCanvas.Height = icTaskGrid.ActualHeight;
        }
        #endregion

<DownLoad>

タスク管理ツールについて

ガントチャートのソフトを放置して、ずっと作っていたタスクを管理するソフトがいい感じにできてきた。
インストール不要で共有フォルダにファイルを配置すれば複数人でも同時に触ることができる。また、自分の関係するタスクにコメントや変更が加わったら都度確認を行うことができるウィンドウや、一覧検索画面、分析用のグルーピング機能なども詰め込んでみた。

もう少しバグ取りをしたらVectorにでも登録してみようかと思う

・メイン画面



・検索画面


・検索画面(グルーピング)



sqlite3を.NETで利用する


いろんなソフトのデータの保存方法についてシリアライズ化して保存したXmlファイルをまとめて
Zipファイルにすることで対応しようとしていたが、複数人で触ることを考えてsqliteのほうがトラブルが少なそうだったので
データの保存の関連を修正した。

データの取得については汎用性を持たせたかったので、DataSet、DataTableで取得するようにしておいた。
データを取得するときにインテリセンスが効かないので微妙な気もするが、
SQL側で集計したり、分析した結果を表示したりして列名を色々作ることが多そうなのでDataTableを利用したほうが融通が利く気がする。
融通を聞かせすぎると統一感がなくなるけど、どうせそこまで統一したシステムなんて組めないからいいんじゃないかな・・・。

インジェクション対策としてパラメータも利用できるようにしてみた。

sqlite以外でもコネクション周りを修正すれば利用できるDataBaseAccessクラスを作成した。
SQL Serverの場合であればSQLiteConnectionStringBuilderなどを軒並みSQLConnectionに変更すれば対応できる、気がする。
※SQL文の書き方が違うのでそのあたりは頑張っていただくしかないが・・・。
 どこかの機会に遅いSQLを早くするコツも書いてみたいと思う。いつになるかわからないけど。

そのときにサンプルプログラムを作成し、調査した内容をメモしておく。

sqliteをNuGetパッケージの管理からインストールし、参照の追加を行う




下記の using を追加する。

  using System.Data;
  using System.Data.SQLite;


↓↓↓↓↓ サンプルソース ↓↓↓↓↓

private void btnSample_Click(object sender, RoutedEventArgs e)
{
    string path = @"C:\test\SampleDataBase.db";
    var sqlConnectionSb = new SQLiteConnectionStringBuilder { DataSource = path };

    // ▽▽▽ データベース作成 ▽▽▽
    DataBaseAccess dataBaseAccess = new DataBaseAccess();
    dataBaseAccess.CreateDataBase_NewFile(path);
    // △△△ データベース作成 △△△


    // ▽▽▽ データ登録処理 ▽▽▽

    List<DataBaseAccess.ExecutionSQL> lstInsUpd = new List<DataBaseAccess.ExecutionSQL>();

    //   データ登録(ファイル実体)
    DataBaseAccess.ExecutionSQL InsSqlFile = new DataBaseAccess.ExecutionSQL(DataBaseAccess.ExecutionSQL.SqlType.INSERT);
    InsSqlFile.ExecuteSQL = " INSERT INTO T_File(intProjectNo,intCardNo,txtCommentNo,intFileNo,bloFile)  "
                              + "             VALUES(@intProjectNo , @intCardNo , @txtCommentNo , @intFileNo , @bloFile) ";
    InsSqlFile.Parameters.Add(new DataBaseAccess.SQLPara("@intProjectNo", "1"));
    InsSqlFile.Parameters.Add(new DataBaseAccess.SQLPara("@intCardNo", "1"));
    InsSqlFile.Parameters.Add(new DataBaseAccess.SQLPara("@txtCommentNo", "0"));
    InsSqlFile.Parameters.Add(new DataBaseAccess.SQLPara("@intFileNo", "1"));
    //      ファイルをByte型配列で読み込み
    var data = System.IO.File.ReadAllBytes(@"C:\test\sample.txt");
    InsSqlFile.Parameters.Add(new DataBaseAccess.SQLPara("@bloFile", data));
    lstInsUpd.Add(InsSqlFile);

    //   データ登録(ファイル情報)
    DataBaseAccess.ExecutionSQL InsSqlFileInfo = new DataBaseAccess.ExecutionSQL(DataBaseAccess.ExecutionSQL.SqlType.INSERT);
    InsSqlFileInfo.ExecuteSQL = " INSERT INTO T_FileInfo(intProjectNo,intCardNo,txtCommentNo,intFileNo,txtFileName)  "
                              + "             VALUES(@intProjectNo , @intCardNo , @txtCommentNo , @intFileNo , @txtFileName) ";
    InsSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@intProjectNo", "1"));
    InsSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@intCardNo", "1"));
    InsSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@txtCommentNo", "0"));
    InsSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@intFileNo", "1"));
    InsSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@txtFileName", "sample.txt"));
    lstInsUpd.Add(InsSqlFileInfo);

    //   データ登録実行
    dataBaseAccess.ExecuteSQL_InsUpdDel(path, lstInsUpd);

    // △△△ データ登録処理 △△△

    // ▽▽▽ データ取得処理 ▽▽▽

    List<DataBaseAccess.ExecutionSQL> lstSelect = new List<DataBaseAccess.ExecutionSQL>();

    //   データを取得するためのSQL(ファイル実体)
    DataBaseAccess.ExecutionSQL SelSqlFile = new DataBaseAccess.ExecutionSQL(DataBaseAccess.ExecutionSQL.SqlType.SELECT);
    SelSqlFile.TableName = "T_File";
    SelSqlFile.ExecuteSQL = " SELECT * FROM  T_File ";
    lstSelect.Add(SelSqlFile);

    //   データを取得するためのSQL(ファイル情報)
    DataBaseAccess.ExecutionSQL SelSqlFileInfo = new DataBaseAccess.ExecutionSQL(DataBaseAccess.ExecutionSQL.SqlType.SELECT);
    SelSqlFileInfo.TableName = "T_FileInfo";
    SelSqlFileInfo.ExecuteSQL = " SELECT * FROM  T_FileInfo WHERE intProjectNo = @intProjectNo ";
    SelSqlFileInfo.Parameters.Add(new DataBaseAccess.SQLPara("@intProjectNo", "1"));
    lstSelect.Add(SelSqlFileInfo);

    //   SQLを実行し、結果をDataSetで取得する
    DataSet ds = dataBaseAccess.GetDatas(path, lstSelect);

    //   取得したファイルを別名で出力する
    System.IO.File.WriteAllBytes(@"C:\test\output.txt", (byte[])ds.Tables["T_File"].Rows[0]["bloFile"]);

    // △△△ データ登録処理 △△△

}


/// <summary>
/// データベースアクセス関連
/// </summary>
public class DataBaseAccess
{

    #region Public Method

    /// <summary>
    /// 新規ファイル作成時のSQL
    /// </summary>
    /// <param name="strFilePath"></param>
    public void CreateDataBase_NewFile(string strFilePath)
    {
        ExecuteSQL_DDL(strFilePath, _GetCreateTableSQL_V0100());
    }

    /// <summary>
    /// SQL実行処理
    /// </summary>
    /// <param name="strFilePath"></param>
    /// <param name="ExecutionSQL"></param>
    /// <returns></returns>
    public DataSet GetDatas(string strFilePath, ExecutionSQL ExecutionSQL)
    {
        List<ExecutionSQL> lst = new List<ExecutionSQL>();
        lst.Add(ExecutionSQL);
        return GetDatas(strFilePath, lst);
    }

    /// <summary>
    /// SQL実行処理
    /// </summary>
    public DataSet GetDatas(string strFilePath, List<ExecutionSQL> ExecutionSQLs)
    {
        var sqlConnectionSb = new SQLiteConnectionStringBuilder { DataSource = strFilePath };
        DataSet dataSet = new DataSet();

        try
        {
            using (var cn = new SQLiteConnection(sqlConnectionSb.ToString()))
            {
                cn.Open();

                using (var cmd = new SQLiteCommand(cn))
                {
                    foreach (var param in ExecutionSQLs)
                    {
                        // ↓↓↓ チェック ↓↓↓
                        if (param.Type != ExecutionSQL.SqlType.SELECT) { throw new Exception("GetDatasメソッドはSELECT構文のみが実行可能です。SqlTypeを確認してください。"); }
                        // ↑↑↑ チェック ↑↑↑

                        cmd.CommandText = param.ExecuteSQL;

                        // SQLパラメータを設定
                        cmd.Parameters.Clear();
                        foreach (var item in param.Parameters) { cmd.Parameters.Add(new SQLiteParameter(item.Name, item.Value)); }

                        // SQLを実行し、データを取得する
                        using (var reader = cmd.ExecuteReader())
                        {
                            DataTable dt = new DataTable(param.TableName);
                            DataRow dr;

                            for (int i = 0; i < reader.FieldCount; i++)
                            {
                                dt.Columns.Add(new DataColumn(reader.GetName(i), reader.GetFieldType(i)));
                            }

                            while (reader.Read() == true)
                            {
                                dr = dt.NewRow();
                                for (int i = 0; i < reader.FieldCount; i++)
                                {
                                    dr[i] = reader[i];
                                }
                                dt.Rows.Add(dr);
                            }

                            dataSet.Tables.Add(dt);

                            reader.Close();
                        }

                    }

                }
            }

        }
        catch (Exception)
        {

            throw;
        }

        return dataSet;

    }

    /// <summary>
    /// SQL実行時の設定内容
    /// </summary>
    public class ExecutionSQL
    {
        public ExecutionSQL(SqlType sqlType)
        {
            this.Parameters = new List<SQLPara>();
            this.Type = sqlType;
        }

        public enum SqlType
        {
            INSERT, UPDATE, DELETE, SELECT
        }

        /// <summary>
        /// SELECT 構文の時には必須
        /// INSERT,UPDATE,DELETE 構文の時には不要
        /// </summary>
        public string TableName;

        /// <summary>
        /// 実行するSQL文
        /// </summary>
        public string ExecuteSQL;

        /// <summary>
        /// SQLのパラメータ
        /// </summary>
        public List<SQLPara> Parameters;

        public SqlType Type;
    }

    /// <summary>
    /// SQLパラメータ
    /// </summary>
    public class SQLPara
    {
        public SQLPara(string name, object value)
        {
            this.Name = name;
            this.Value = value;
        }
        public string Name;
        public object Value;
    }

    /// <summary>
    /// 登録・更新・削除のSQLを実行するためのメソッド
    /// </summary>
    public void ExecuteSQL_InsUpdDel(string strFilePath, List<ExecutionSQL> ExecutionSQLs)
    {
        var sqlConnectionSb = new SQLiteConnectionStringBuilder { DataSource = strFilePath };

        try
        {
            using (var cn = new SQLiteConnection(sqlConnectionSb.ToString()))
            {
                cn.Open();
                using (var cmd = new SQLiteCommand(cn))
                {
                    using (SQLiteTransaction trans = cn.BeginTransaction())
                    {

                        foreach (var param in ExecutionSQLs)
                        {
                            // ↓↓↓ チェック ↓↓↓
                            if (param.Type == ExecutionSQL.SqlType.SELECT) { throw new Exception("ExecuteSQL_InsUpdDelメソッドはSELECT構文以外のみが実行可能です。SqlTypeを確認してください。"); }
                            // ↑↑↑ チェック ↑↑↑

                            cmd.CommandText = param.ExecuteSQL;

                            // SQLパラメータを設定
                            cmd.Parameters.Clear();
                            foreach (var item in param.Parameters) { cmd.Parameters.Add(new SQLiteParameter(item.Name, item.Value)); }

                            cmd.ExecuteNonQuery();

                        }

                        trans.Commit();
                    }

                }

            }
        }
        catch (Exception)
        {

            throw;
        }
    }

    #endregion

    #region Private Method 1st.

    /// <summary>
    /// DDL構文(Create、Drop)系列のSQLを実行する
    /// </summary>
    /// <param name="strFilePath">ファイルパス</param>
    /// <param name="strSQL">実行SQL</param>
    private void ExecuteSQL_DDL(string strFilePath, string strSQL)
    {
        var sqlConnectionSb = new SQLiteConnectionStringBuilder { DataSource = strFilePath };

        using (var cn = new SQLiteConnection(sqlConnectionSb.ToString()))
        {
            cn.Open();
            using (var cmd = new SQLiteCommand(cn))
            {
                cmd.CommandText = strSQL;
                cmd.ExecuteNonQuery();
            }
        }
    }

    #region Private Method 2st.

    /// <summary>
    /// テーブル作成処理(バージョン01.00)
    /// </summary>
    /// <returns></returns>
    private string _GetCreateTableSQL_V0100()
    {
        string strSQL = "";
        strSQL = " create table T_File ( "
                + "   intProjectNo integer "
                + "   , intCardNo integer"
                + "   , txtCommentNo TEXT "
                + "   , intFileNo integer"
                + "   , bloFile BLOB "
                + "   , constraint T_File_PKC primary key (intProjectNo,intCardNo,txtCommentNo,intFileNo) "
                + " ); "
                + "  "
                + " create table T_FileInfo ( "
                + "   intProjectNo integer"
                + "   , intCardNo integer"
                + "   , txtCommentNo TEXT "
                + "   , intFileNo integer"
                + "   , txtFileName TEXT "
                + "   , constraint T_FileInfo_PKC primary key (intProjectNo,intCardNo,txtCommentNo,intFileNo) "
                + " ); ";
        return strSQL;
    }

    #endregion

    #endregion

}

↑↑↑↑↑ サンプルソース ↑↑↑↑↑

AvalonDockについて

作成中のアプリのサイドバーにExtenderコントロールでサブ情報を表示するようにしていたが、どうにも見た目が気に入らないのでIDEみたいなドッキングウィンドウ(?)にしてみたくて色々調べていると有名どころを見つけたので利用してみた。

1.インストール。


2.ソースコード
Windowのところに
「xmlns:xcad="http://schemas.xceed.com/wpf/xaml/avalondock"」を追加しておいて、下記のようなソースを記載

            <xcad:DockingManager
                           AllowMixedOrientation="True"
                           BorderBrush="Black"
                           BorderThickness="1" >
                <xcad:DockingManager.DocumentHeaderTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <Image Source="{Binding IconSource}" Margin="0,0,4,0" Height="5" Width="5" MaxHeight="5" MaxWidth="5" Stretch="None" />
                            <TextBlock Text="{Binding Title}" />
                        </StackPanel>
                    </DataTemplate>
                </xcad:DockingManager.DocumentHeaderTemplate>
                <xcad:LayoutRoot x:Name="_layoutRoot">

                    <xcad:LayoutRoot.LeftSide>
                        <xcad:LayoutAnchorSide>
                            <xcad:LayoutAnchorGroup>
                                <xcad:LayoutAnchorable Title="プロジェクト" ContentId="Project" IconSource="images/document.png" CanClose="False" CanHide="False" >
                                    <StackPanel>
                                        <ItemsControl x:Name="tSxtack" ItemsSource="{Binding Projects}">
                                            <ItemsControl.ItemsPanel>
                                                <ItemsPanelTemplate>
                                                    <StackPanel Orientation="Vertical">
                                                    </StackPanel>
                                                </ItemsPanelTemplate>
                                            </ItemsControl.ItemsPanel>
                                            <ItemsControl.ItemTemplate>
                                                <DataTemplate>
                                                    <DockPanel>
                                                        <Expander Style="{DynamicResource ExpanderCustomStyle}" >
                                                            <Expander.Header>
                                                                <StackPanel Orientation="Horizontal">
                                                                    <CheckBox Content="{Binding Name}" IsChecked="{Binding Showing}" ></CheckBox>
                                                                    <Button Name="btnProjectDetail" Content="..." Background="Transparent"  Width="25" BorderBrush="Silver"  BorderThickness="0.5" FontFamily="MS Gothic" Click="btnProjectDetail_Click"/>
                                                                </StackPanel>
                                                            </Expander.Header>
                                                            <StackPanel Margin="25,0,0,0" >
                                                                <TextBlock Text="{Binding StartDay,StringFormat=開始日:{0:yy/MM/dd}}"></TextBlock>
                                                                <TextBlock Text="{Binding EndDay,StringFormat=終了日:{0:yy/MM/dd}}"></TextBlock>
                                                                <TextBlock Text="{Binding Info}"></TextBlock>
                                                            </StackPanel>
                                                        </Expander>
                                                    </DockPanel>
                                                </DataTemplate>
                                            </ItemsControl.ItemTemplate>
                                        </ItemsControl>
                                    </StackPanel>
                                </xcad:LayoutAnchorable>
                                <xcad:LayoutAnchorable x:Name="AvalondockDelayCard" Title="遅延カード" ContentId="DelayCardList" IconSource="images/document.png" CanClose="False" CanHide="False" >
                                    <ListView Name="lvLimitCardList" ItemsSource="{Binding DelayCardList}">
                                        <ListView.View>
                                            <GridView>
                                                <GridViewColumn Header="タイトル" Width="120" DisplayMemberBinding="{Binding CardTitle}" />
                                                <GridViewColumn Header="メンバー" Width="50" DisplayMemberBinding="{Binding Members}" />
                                            </GridView>
                                        </ListView.View>
                                        <ListView.GroupStyle>
                                            <GroupStyle>
                                                <GroupStyle.ContainerStyle>
                                                    <Style TargetType="{x:Type GroupItem}">
                                                        <Setter Property="Template">
                                                            <Setter.Value>
                                                                <ControlTemplate>
                                                                    <Expander IsExpanded="True" >
                                                                        <Expander.Header>
                                                                            <StackPanel Orientation="Horizontal">
                                                                                <!-- GroupItemのNameプロパティをバインド -->
                                                                                <TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="Gray" FontSize="22" VerticalAlignment="Bottom" />
                                                                                <TextBlock Text="{Binding ItemCount}" FontSize="22" Foreground="Green" FontWeight="Bold" FontStyle="Italic" Margin="10,0,0,0" VerticalAlignment="Bottom" />
                                                                                <TextBlock Text=" item(s)" FontSize="22" Foreground="Silver" FontStyle="Italic" VerticalAlignment="Bottom" />
                                                                            </StackPanel>
                                                                        </Expander.Header>
                                                                        <ItemsPresenter />
                                                                    </Expander>
                                                                </ControlTemplate>
                                                            </Setter.Value>
                                                        </Setter>
                                                    </Style>
                                                </GroupStyle.ContainerStyle>
                                            </GroupStyle>
                                        </ListView.GroupStyle>
                                    </ListView>
                                </xcad:LayoutAnchorable>
                            </xcad:LayoutAnchorGroup>
                        </xcad:LayoutAnchorSide>
                    </xcad:LayoutRoot.LeftSide>


                    <xcad:LayoutPanel Orientation="Horizontal">
                        <xcad:LayoutAnchorablePane DockWidth="200">
                            <xcad:LayoutAnchorable ContentId="MainWindow" Title="メイン画面" CanHide="False" CanClose="False" CanFloat="True" AutoHideWidth="240" CanAutoHide="False" IconSource="images/dokking.png">
                                <DockPanel>
                                    <ListView ItemsSource="{Binding CardLists,Mode=TwoWay}" ScrollViewer.VerticalScrollBarVisibility="Hidden" Style="{StaticResource NormalListView}">
                                        <ListView.ItemsPanel>
                                            <ItemsPanelTemplate>
                                                <StackPanel Orientation="Horizontal"/>
                                            </ItemsPanelTemplate>
                                        </ListView.ItemsPanel>

                                        <ListView.ItemTemplate>
                                            <DataTemplate>
                                                <DockPanel Height="{Binding ActualHeight,RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}">
                                                    <control:CardList Margin="10" CardChanged="CardList_CardChanged"
                                          PreviewMouseLeftButtonDown="CardList_PreviewMouseLeftButtonDown"
                                          PreviewMouseMove="CardList_PreviewMouseMove"
                                          PreviewMouseUp="CardList_PreviewMouseUp"
                                          PreviewDrop="CardList_PreviewDrop"
                                          AllowDrop="True"
                                          PreviewDragEnter="CardList_PreviewDragEnter"
                                          PreviewDragLeave="CardList_PreviewDragLeave"
                                          />
                                                </DockPanel>
                                            </DataTemplate>
                                        </ListView.ItemTemplate>
                                    </ListView>
                                </DockPanel>
                            </xcad:LayoutAnchorable>
                        </xcad:LayoutAnchorablePane>
                    </xcad:LayoutPanel>

                </xcad:LayoutRoot>
            </xcad:DockingManager>

3.結果


本当はDataGridとかもWPF Extended Toolkitにあるものを使おうかと思ったけど各機能の資料とかサンプルが見つけられなかったので早々に諦めた。

早く公開できるレベルまで仕上げたい・・・・!!



久しぶりの更新

半年ぶりぐらいで更新。
勉強がてら、趣味グラマーとしてずっとちょこちょこ作ってきたアプリがそれっぽく出来てきた。

タスク内容をカードに書き込んでそのタスクを各担当者に振り分け看板方式のように状態が確認できるアプリ。
っていうか「Trello」みたいなアプリをオフラインで使いたかったから頑張ってみた。

共有フォルダに配置すれば複数人でも更新できる。ようになるはず。
保存されたデータはExcelやWordと同じようにZip形式にして拡張子の名前を変えて保存。
データを修正しているときには一時的にZipファイルを解凍し、隠しフォルダに展開して展開されたものを修正するような仕組みを実装してみた。

WPFならDataBindingで結構作れるなーと思いながら、頑張ってみた。

間違いなく、「Trello」が使える環境ならそっちのほうが便利。
(Trello使ったことないけど)

データをネットにアップしたくない場合とかには使える。かもしれない。

まぁWPFの勉強がてらのんびり作っていこうと思う。
上手くできたら、フリーで公開してみたいなぁと思ってみたり。

最後はProjectManagerと合わせ技ができたらいいなーと思ってはいるけどいつになるやら・・・。


PowerShellでWPFアプリケーションをBindingするときの注意点

 1.PowerShellでWPFアプリケーションのBindingについて マニアックだけどPowerShellでWPFアプリケーションを作っている。INotifyChangedを実装したいけど、PowerShellにはgetterやsetterがないので通知が発行できない。 そ...