2014年11月14日金曜日

寸法×個数を、MultiBindingとIMultiValueConverterで

幅×個数 という感じのデータが有りまして、丁度、MultiBindingが活躍しました。

2通りの表現を、2つのフィールドで。

(1) 幅
(2) 幅x個数

幅を入力したい。しかし、分割する場合も有る。では、最初は3つだけ分割→幅1,幅2,幅3。いやいや、そうではなく、何個かに分ける場合も有る→幅1x幅1個数,幅2x幅2個数,幅3x幅3個数。これでも表現できないものは備考に、という流れになりました。

MultiBindingの性質として、「入力は1つ以上」「出力は1つだけ」という条件が有ります。丁度これに合っています。

XAML:

<GridViewColumn Header="W1" Width="70" local:LvSortSpec.Columns="幅1" local:GvcManager.Key="幅1" >
 <GridViewColumn.CellTemplate>
  <DataTemplate>
   <TextBlock HorizontalAlignment="Center">
    <TextBlock.Text>
     <MultiBinding Converter="{StaticResource 寸法個数Converter}">
      <Binding Path="幅1" />
      <Binding Path="幅1個数" />
     </MultiBinding>
    </TextBlock.Text>
   </TextBlock>
  </DataTemplate>
 </GridViewColumn.CellTemplate>
</GridViewColumn>

お分かりとは思いますが、 "寸法個数Converter"を、リソース辞書で定義するようにしてください。

<local:寸法個数Converter x:Key="寸法個数Converter" />

C#

 public class 寸法個数Converter : IMultiValueConverter {
  public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
   if (true
    && values != null
    && values.Length == 2
   ) {
    if (!object.ReferenceEquals(values[0], DependencyProperty.UnsetValue)) {
     if (!object.ReferenceEquals(values[1], DependencyProperty.UnsetValue)) {
      return values[0] + "×" + values[1];
     }
     return "" + values[0];
    }
   }
   return "";
  }

  public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) {
   throw new NotImplementedException();
  }
 }

DependencyProperty.UnsetValue が出現します。値が無い場合、nullか、DBNull.Valueが来るのかと思いきや、違って、こういう値がくるようです。

2014年11月13日木曜日

BindingしているTextBoxで、閉じた時に更新されない

次の2行のコードは恐らく等価でありますので、フォーカスを失うまで、Bindingは書き戻しされません。

<TextBox Text="{Binding 品番}" />
<TextBox Text="{Binding 品番,UpdateSourceTrigger=LostFocus}" />

特にWindowを閉じた場合、書き戻しされない事が有り、結構困ったりします。

次のように書けば解決なのですが、全てに UpdateSourceTrigger=PropertyChanged を追加していくのも気が遠くなる話です。

<TextBox Text="{Binding 品番,UpdateSourceTrigger=PropertyChanged}" />

そこで、BindingGroupを使って、楽をしています。こちらに詳細を残しています。

*但し、この方法には副作用が有ります。
DataRowViewへアクセスする前に、BindingGroup.CommitEdit(); を呼ばないと、DataRowViewに反映されません。
メソッドの入り口にCommitEditが多くなるきらいが有りますが… そこはバランスを考えます。

WPFで、表示しないWindowを作るとメモリリーク発生か

WPFの設計思想をよくしらないのでアレなのですが、、、

作成したWindowをCloseしないとメモリリークの原因になり、アプリが終了しません。。。

namespace WpfApplication17 {
    /// <summary>
    /// App.xaml の相互作用ロジック
    /// </summary>
    public partial class App : Application {
        protected override void OnStartup(StartupEventArgs e) {
            Window w1 = new Window();
            w1.Close();
            Window w2 = new Window();
            w2.Close();
        }
    }
}

上の例はわかりやすいと思います。何も表示しないで終了するWPFアプリです。きちんと終了してくれます。

namespace WpfApplication17 {
    /// <summary>
    /// App.xaml の相互作用ロジック
    /// </summary>
    public partial class App : Application {
        protected override void OnStartup(StartupEventArgs e) {
            Window w1 = new Window();
            w1.Close();
            Window w2 = new Window();
            //w2.Close();  <-- 閉じないようにする
        }
    }
}

上の例はw2.Close()をコメントにしました。終了しません。

とにかく、作ったWindow等は
  • 表示したら、閉じる。
  • 表示しない場合は、意図的にClose()を呼ぶ。
これらをしない事には、アプリが終了しないという事でした。。。

2014年10月16日木曜日

AddNewした後、EndEditを呼ぶべし

DataTable/DataViewをWPFに対してバインドする場合、DataRowViewがくっ付きますよ、という話は以前致しました。

さて、見積もり作成ソフトHiramituで、新しい見積もりを作成(DataView.AddNew)します。次のように、EndEditを呼んで置かないと、WPFのバインドに変更が伝わらない、という事が起こるようです。

        private void m追加_Click(object sender, RoutedEventArgs e) {
            if (T1 == null) return;

            var form = new T見積INPUT();
            var rec = T1.View.AddNew();
            rec["作成日"] = DateTime.Today;
            rec["有効期限"] = DateTime.Today.AddDays(14);
            rec.EndEdit(); // <--- ここ!
            App.ParentCenter(form);
            form.T1 = T1;
            cvsItems.View.MoveCurrentTo(rec);
            if (form.ShowDialog() ?? false) {
                rec.EndEdit();
            }
            else if (FConfirm.Show("新しいのん削除するよ?") ?? false) {
                rec.Delete();
            }
            T1.Save1(rec);
        }


DataRowViewを変更したのに、WPFへ変更が伝わらない。。。想像の範囲ですが、IsEditが関連すると思います。

例とは異なるコードですが、実験しました所、こういうことでした。

            ((DataRowView)tb.DataContext).BeginEdit();
            ((DataRowView)tb.DataContext)["Value"] = new Random().Next() + "";
            ((DataRowView)tb.DataContext).EndEdit(); // ← ここで変更が通知される

または、編集中でない場合、つまり、BeginEditとEndEditを省略した場合、ただちに通知されます。

            ((DataRowView)tb.DataContext)["Value"] = new Random().Next() + ""; // ← ここで通知される

2014年8月21日木曜日

RibbonWindowで、左上のアイコンダブルクリック→終了してしまう

ウィンドウを閉じる方法はいくつも有りますが、RibbonWindowを使う場合は注意が必要です。
  • 左上のアイコンをダブルクリック → 【画面と一緒に、アプリが終了します】
  • プログラムで閉じる
  • 右上の赤バッテンで閉じる

回避策:

言葉で説明が難しい。。。ウィンドウごとに、自分でComamndBindingを追加するらしいです。

参考: http://social.msdn.microsoft.com/Forums/ja-JP/c9c96889-44bd-474a-a795-d56335fcdc0c/wpfmicrosoft-ribbon-for-wpfribbonwindowclosing?forum=wpfja

コード例:

AppクラスにInstallNoexitメソッドを追加。

    public partial class App : Application {
        public static void InstallNoexit(Microsoft.Windows.Controls.Ribbon.RibbonWindow win) {
            // http://social.msdn.microsoft.com/Forums/vstudio/en-US/c91438bc-4b12-47b2-89e2-13817b3689e4/ribbonwindow-doubleclick-causing-application-shutdown?forum=wpf
            win.CommandBindings.Add(
                new System.Windows.Input.CommandBinding(
                    System.Windows.Input.ApplicationCommands.Close,
                    (sender, e) => {
                        ((Microsoft.Windows.Controls.Ribbon.RibbonWindow)sender).Close();
                    }));
        }
コンストラクタで、App.InstallNoexit(this);のように、呼び出す。
    public partial class T見積INPUT : RibbonWindow {
        public T見積INPUT() {
            InitializeComponent();
            App.InstallNoexit(this);

2014年5月31日土曜日

RibbonControlsLibraryで、タイプ初期化子が例外をスロー

NuGetでRibbonControlsLibrary.dllを入手し、添付できるようになっています。

ところが残念なことに、本番環境では次のような例外を起こす場合が有ります。

『'Microsoft.Windows.Controls.Ribbon.RibbonWindow' のタイプ初期化子が例外をスローしました。』

$exception.InnerExceptionを見れば理由が分かるのですが、別途、Microsoft.Windows.Shell.dllが必要です。

場所例:
C:\Program Files (x86)\Microsoft Ribbon for WPF\V3.5\Microsoft.Windows.Shell.dll

入手はこちらなどから:

Microsoft Ribbon for WPF October 2010
http://www.microsoft.com/en-us/download/details.aspx?id=11877

2014年5月27日火曜日

SAHクラス, SQL Auto Hourglass, WaitCursor的な物

SQLのクエリ中に砂時計を表示する。usingで囲い込みできればGoodと思い、WPFへ移植。

    public class SAH : IDisposable {
        Cursor c;
        readonly static Cursor a = new Cursor(new MemoryStream(Resources.SAH, false));

        public SAH() {
            c = Mouse.OverrideCursor;
            Mouse.OverrideCursor = a;
        }

        #region IDisposable メンバ

        public void Dispose() {
            Mouse.OverrideCursor = c;
        }

        #endregion
    }

リソースSAHを定義してください。
カーソルファイル(SAH.cur等)を、
バイナリ形式(System.Byte[])で取り込んでおけば、
Okです。

DataContextには、DataTable or DataView?

CollectionViewSource.Source経由で、DataContextにレコードを設定するのですが、、、

DataView →→→ WPFに変更が通知されない!

DataTable →→→ WPFに変更が通知される。

(5/27追記) しかし問題点が。。。

DataTableはフィルタ・並び替えが使えません。。。

(5/30追記) すべて気のせいでした。CollectionViewSource.SourceDataTableを突っ込むと、DataTable.DefaultViewが使用される動きを確認しました。

(10/16追記) 変更が通知されない!点に関しては、私の勘違いであったようです。DataRowViewの編集中(IsEditがtrue)の場合は、変更の通知が抑制されるだけで、最後にEndEdit/CancelEditを使えば、変更が伝わりました。

2014年5月16日金曜日

RunはMode=OneWayでご利用を

Hiramituというアプリを作っています。
Excelで作っている見積書の作成&印刷を、SQL Server&WPFでやろう、という算段です。

印刷も根性書きのXAMLでやっています。

Runを使うときの注意事項…

Mode=OneWayにして使う。何故かバインド先に書き戻そうとする動きが有り、プログラムがハングする為です。

    <Label Grid.Column="1" Grid.ColumnSpan="3" HorizontalContentAlignment="Right" BorderBrush="Black" BorderThickness="0,0,1,1" Padding="{StaticResource TablePadding}">
        <TextBlock>
            <Run>小計:</Run>
            <Run Text="{Binding 金額,StringFormat='#,##0',TargetNullValue=0,Mode=OneWay}" />
        </TextBlock>
    </Label>

こうやって書きますと、Gridの中に、Labelを一つ置くだけで済みます。

2014年5月15日木曜日

WPF で使うデータベース技術について

※ Entity Framework 6 + INotifyPropertyChanged の悩みでしたらこちらへ。

様々な方向性から検討した結果、次の構成に決めました:
  • データベースは、PostgreSQL 9.2を利用。
  • 画面・フロントエンドの構築は、WPFを利用。
  • データベースとの接続には、Npgsqlと、型指定の無いDataSet技術を利用。

Entity Frameworkとの決別

DataSet技術を使う前は、Entity Frameworkを使っていました。しかし、止めました。

Entity Frameworkを止めた理由とは:
  • テーブルの主キーを後から変更することができない
    『後から担当者IDを変更できないのは困る…』
  • 外部キー制約の制限。主キー以外でリレーションを構築できない。改善の要望が上がっています。
    『serial型の主キーを別途追加し、担当者IDはUnique制約にしておいて、それでリレーションを組もうか、ってできない!?』

そこで、Entity Frameworkの利用を断念致しました。

その後の模索等

VS Express版で開発しています。無条件に、金銭コストが掛からない方法を選んでいました。

どうしても手抜きがしたくて、SELECT/UPDATE/INSERT/DELETEの生成を自動化してくださるORMの手が欠かせません。

dapperも試しました。これはDBの更新ができないので、断念致しました。

VSのDataSet Designerも試しました。これは、DBの更新が簡略化できます。しかし、nullの扱いに難があるので、断念致しました。(Generatorが、Nullableを使ったコードを生成できないので、意図的にIsNull/SetNullを呼び出す必要が有る為)

DataSetとの邂逅

Visual Studioが如何様なコードを出力するのか、興味が有りました。DataSetとWPFがバインディングする際に。

すると、非常に気の利いたコードが生成されたのです。

こういう感じの事を実現するコードが生成されます。

(DataContext = CollectionViewSource)⇔DataTable

具体的には、次のコードが生成されました。
    <Window.Resources>
        <local:DataSet1 x:Key="dataSet1"/>
        <CollectionViewSource x:Key="dataTable1ViewSource" Source="{Binding DataTable1, Source={StaticResource dataSet1}}"/>
    </Window.Resources>

    <Grid DataContext="{StaticResource dataTable1ViewSource}">

    </Grid>

今回出現しましたCollectionViewSourceは、ナビゲーション機能付きです!

DataViewの、
  • 先頭(MoveCurrentToFirst)
  • 前(MoveCurrentToPrevious)
  • 次(MoveCurrentToNext)
  • 最後(MoveCurrentToLast)
  • 所定のDataRowViewに移動(MoveCurrentToPosition)
移動する機能が付いています。

これはいい!

具体的に、良いと思った点:
  • DataContextを変更しなくても良い。
    • ナビゲーション機能を実装しようとすると、DataContextを変更することを考えがちです。しかし、それをしなくても良い。
    • ずっと同じCollectionViewSourceを指して置くだけで良いのです。
  • 双方向Bindingができる。
    • MoveCurrentXXXした後、{Binding 担当者ID}と書いている部分は自動で更新されます。
    • TextBoxで、Text="{Binding 担当者ID}"にしている場合、Textを変更したらデータソース側も自動で更新されます。

※途中からDataTable系→DataView系に変化しています。そのような動きをしているので、そのように書いています ^^

という訳で、DataSet技術CollectionViewSourceの組み合わせて乗り切ることに決めました。

しかし、CollectionViewSource.Viewには、考慮が必要な難点もあります。ついでに:
  • 先頭よりも前に行くことができる。IsCurrentBeforeFirstで判定。
  • 最後よりも後に行くことができる。IsCurrentAfterLastで判定。
  • 先頭かどうかは、CurrentPosition == 0 で判定できるが、
  • 最後かどうかは、CurrentPosition == cvsItems.View.Cast<Object>().Count() 等、列挙してみないと数を勘定できない。

WPFで業務アプリ開発!

前期から、生産管理的なシステムを、近代的なWindows環境で動くように、移植しています。

その奮闘によって得られた知見・知識を発信していきたい。