2015年3月20日金曜日

WPFの印刷画面はCanvasかGridか

最終的には、Gridに統一しました。

Canvasですと、後で余白を追加する際に、融通が利かなかった為です。

Gridは意外に便利でした。
アンカーを付ける(例:HorizontalAlignment="Stretch")ことができるので、急な寸法変更にも楽に対応できます。

ちなみに、Cavnasを使うと、XAMLの書き方は、こんな感じです。

<Canvas>
  <!-- 表のセル、枠線付きで -->
  <Label Content="{Binding xxx}" 
         Padding="3,1,0,1" 
   BorderBrush="Black" BorderThickness="1" 
   Canvas.Left="53" Canvas.Top="56" />
  ...
</Canvas>

Canvas.LeftとCanvas.Topが即値です。レイアウトが崩れにくくていい感じかもしれませんが、いざ横幅を縮めるとなると、再調整に掛かる重労働は必至です。。。
Labelを使うのは、枠線を付けられるからです。TextBlockでは、枠線が付きません。

問題点:
  • 箱のかたまりを、他の場所にコピーしたい場合。Canvas.LeftやCanvas.Topの数値を後から手で触るのは大変そう。
  • 余白を付けずに用紙を設計してしまった場合。後でMargin="20"を追加すると、かなり幅が縮みます。

課題山盛りで、試行錯誤を致しました。

そこで、次の様にし、落ち着きました。

<Grid HorizontalAlignment="Left" VerticalAlignment="Top" Margin="4,98,0,0" Height="46" Width="389">
 <Grid.RowDefinitions>
  <RowDefinition Height="21*" /> <!-- ヘッダ -->
  <RowDefinition Height="25*" /> <!-- 金額部分 -->
 </Grid.RowDefinitions>
 <Grid.ColumnDefinitions>
  <ColumnDefinition Width="113*" /> <!-- 発行年月日 -->
  <ColumnDefinition Width="71*" /> <!-- 御得意先 -->
  <ColumnDefinition Width="54*" /> <!-- 担当 -->
  <ColumnDefinition Width="54*" /> <!-- 区分 -->
  <ColumnDefinition Width="97*" /> <!-- 伝票No. -->
 </Grid.ColumnDefinitions>
 <Rectangle Grid.RowSpan="2" Grid.ColumnSpan="5" RadiusY="5" RadiusX="5" Stroke="Black" />
 <Border BorderBrush="Black" BorderThickness="0,1,0,0" Grid.ColumnSpan="5" Grid.Row="1" />
 <Border BorderBrush="Black" BorderThickness="1,0,0,0" Grid.RowSpan="2" Grid.Column="1" />
 <Border BorderBrush="Black" BorderThickness="1,0,0,0" Grid.RowSpan="2" Grid.Column="2" />
 <Border BorderBrush="Black" BorderThickness="1,0,0,0" Grid.RowSpan="2" Grid.Column="3" />
 <Border BorderBrush="Black" BorderThickness="1,0,0,0" Grid.RowSpan="2" Grid.Column="4" />

 <Label Content="発 行 年 月 日" Padding="2" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Column="0" />
 <Label Content="御得意先" Padding="2" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Column="1" />
 <Label Content="担 当" Padding="2" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Column="2" />
 <Label Content="区 分" Padding="2" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Column="3" />
 <Label Content="伝 票 No." Padding="2" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Grid.Column="4" />

 <Label Content="{Binding [日付],StringFormat=yyyy.MM.dd}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Padding="1" Grid.Row="1" Grid.Column="0" />
 <Label Content="{Binding [得コード]}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Padding="1" Grid.Row="1" Grid.Column="1"  />
 <Label Content="{Binding [担当]}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Padding="1" Grid.Row="1" Grid.Column="2"  />
 <Label Content="{Binding [区分]}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Padding="1" Grid.Row="1" Grid.Column="3"  />
 <Label Content="{Binding [伝票番号]}" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Padding="1" Grid.Row="1" Grid.Column="4"  />
</Grid>

プレビュー:


特に機密情報を含まないので、そのまま載せています。

構造は次のようなイメージです:

<Grid>
  Grid横の情報を定義
  Grid縦の情報を定義

  枠線エリア
  Rectangleで、丸い枠を描画します。Grid.ColumnSpanとGrid.RowSpanを明示する必要が有ります。
  Borderで、上だけ線、左だけ線、の形で使って、格子を描画しています。

  ヘッダエリア
  Labelを使っています。Grid.ColumnとGrid.Rowを使い、所定のセルへ格納します。Width/Heightを書く必要は全く有りません。

  ボディーエリア
  Binding中、[日付]の[]は、IDictionary辞書の中を見るので、付けます。
</Grid>

このように記述すると、すっきり致しませんか?

ColumnDefinition Width="113*"と書くと、比率ベースで寸法が決まります。親Gridの大きさが変わったとしても、レイアウトが極端に崩れる心配が有りません。

という事で、この形が目下での理想かな、と考えています。

バーコード印刷は、Imageではなく、Rectangleの集合で

バーコード印刷で、はまりました。

WriteableBitmapを使い、Imageにバーコードの画像をセットします。

それを印刷すれば、完璧なバーコードのエコシステムが出来上がるはずでした。

しかし、実機のバーコードリーダーでは読み取ることができません。

バーの並びというか、配列が変化し、変質しているのです!

試しに、PDFプリンターに印刷したらどうかと思い、試しました所、再現しました。変質しています。

結局、バーコードのビットマップを印刷するのはやめて、Canvasの中にRectangleを詰め込むようにしたら、いけました。

zxingを使ったサンプル。ご利用の際は、自己責任で:

    public class Code39Printer : Viewbox {
        public static DependencyProperty TextProperty = DependencyProperty.Register(
            "Text", typeof(String), typeof(Code39Printer), new PropertyMetadata(
                TextChangedCallback
                )
            );

        public String Text { get { return (String)GetValue(TextProperty); } set { SetValue(TextProperty, value); } }

        static void TextChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            var self = (Code39Printer)d;

            var wr = new Code39Writer();
            bool[] bin = wr.encode(("" + e.NewValue + "").Trim('*').ToUpperInvariant());

            int cx = bin.Length;

            Canvas cv = new Canvas();
            int BW = 1;
            int BH = 20;
            int MH = 3;
            int MW = 10;
            cv.Height = MH + BH + MH;
            cv.Width = MW + cx * BW + MW;
            for (int x = 0; x < cx; x++) {
                if (bin[x]) {
                    Rectangle r = new Rectangle();
                    r.Fill = Brushes.Black;
                    Canvas.SetTop(r, MH);
                    Canvas.SetLeft(r, MW + BW * x);
                    r.Width = BW;
                    r.Height = BH;
                    cv.Children.Add(r);
                }
            }
            self.Child = cv;
        }
    }

使い方、例:

  <MY:Code39Printer Text="{Binding bacord}" xmlns:MY="clr-namespace:YOURNAMESPACE" />

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);