WindowsForms DataGridView コントロール上でのタッチ操作について
Windows FormsのDataGridViewはタッチ操作(フリック、スワイプっていうらしい)でのスクロールに不具合があります。
もうWindows Formsのバグは修正してくれないので回避コードでいくしかないようです。
DataGridView コントロール上でのタッチ操作について – Visual Studio サポート チーム blog
回避コード
private const int WM_VSCROLL = 0x0115; private const int SB_THUMBPOSITION = 0x0004; private const int SB_THUMBTRACK = 0x0005; [System.Runtime.InteropServices.DllImport("user32")] private static extern int PostMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam); public class MyDataGridView : DataGridView { // メッセージを処理します。 protected override void WndProc(ref Message m){ base.WndProc(ref m); if (m.Msg == WM_VSCROLL){ if (LoWord((long)m.WParam) == LoWord((long)SB_THUMBPOSITION)){ BeginInvoke((Action<IntPtr, IntPtr>)((WParam, LParam) => { // SB_THUMBPOSITION を SB_THUMBTRACK に変更します。 IntPtr testWParam = new IntPtr(SB_THUMBTRACK); // WM_VSCROLL メッセージを再送します。 PostMessage(this.Handle, WM_VSCROLL, testWParam, LParam); }), m.WParam, m.LParam); } } } }
WindowsFormsでタッチ操作は正直限界だと思う。
そしてこの一文
※ 上記回避方法については、お客様において十分にご確認、ご検証くださいますようお願いします。
バグ直せや
参考URL:
Windows 8 でタッチ操作でグリッドをスクロールしたときに描画がズレる。(最後が隠れる) - Microsoft.NET - Project Group
Windows8でデータグリッドビューをタッチ操作するとスクロールバーと描画内容がずれる
Windows8 対応タッチパネルでDataGridViewコントロールによるタッチ操作でのMoseMoveイベントが発生しない。 | Microsoft Connect
[WPF] Imageがぼやける問題
Image を使って画像を表示させていると等倍で表示しているはずなのにぼやけてしまいました。
SnapsToDevicePixels や UseLayoutRounding では直らなかったので途方に暮れていたらいいものがありました。
専用のコントロールを作っているので、Imageコントロールに移植します。
※2016/06/03 LayoutUpdatedイベントの処理を追記
using System; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; public class Image : System.Windows.Controls.Image { public Image() { this.LayoutUpdated += Image_LayoutUpdated; } private Point _pixelOffset; void Image_LayoutUpdated( object sender, EventArgs e ) { Point pixelOffset = GetPixelOffset(); if( !AreClose( pixelOffset, _pixelOffset ) ) { InvalidateVisual(); } } private bool AreClose( Point point1, Point point2 ) { return AreClose( point1.X, point2.X ) && AreClose( point1.Y, point2.Y ); } private bool AreClose( double value1, double value2 ) { if( value1 == value2 ) { return true; } double delta = value1 - value2; return ( ( delta < 1.53E-06 ) && ( delta > -1.53E-06 ) ); } protected override Size MeasureOverride( Size constraint ) { // ちょっとだけ修正 BitmapSourceの時だけ処理する var bitmapSource = Source as BitmapSource; if( bitmapSource != null ) { Size measureSize = new Size(); PresentationSource ps = PresentationSource.FromVisual( this ); if( ps != null ) { Matrix fromDevice = ps.CompositionTarget.TransformFromDevice; Vector pixelSize = new Vector( bitmapSource.PixelWidth, bitmapSource.PixelHeight ); Vector measureSizeV = fromDevice.Transform( pixelSize ); measureSize = new Size( measureSizeV.X, measureSizeV.Y ); } return measureSize; } return base.MeasureOverride( constraint ); } protected override void OnRender( System.Windows.Media.DrawingContext dc ) { _pixelOffset = GetPixelOffset(); dc.DrawImage( this.Source, new Rect( _pixelOffset, DesiredSize ) ); } private Point GetPixelOffset() { Point pixelOffset = new Point(); PresentationSource ps = PresentationSource.FromVisual( this ); if( ps != null ) { Visual rootVisual = ps.RootVisual; // Transform (0,0) from this element up to pixels. pixelOffset = this.TransformToAncestor( rootVisual ).Transform( pixelOffset ); pixelOffset = ApplyVisualTransform( pixelOffset, rootVisual, false ); pixelOffset = ps.CompositionTarget.TransformToDevice.Transform( pixelOffset ); // Round the origin to the nearest whole pixel. pixelOffset.X = Math.Round( pixelOffset.X ); pixelOffset.Y = Math.Round( pixelOffset.Y ); // Transform the whole-pixel back to this element. pixelOffset = ps.CompositionTarget.TransformFromDevice.Transform( pixelOffset ); pixelOffset = ApplyVisualTransform( pixelOffset, rootVisual, true ); // エラーが出るので nullの時の処理を追記 if( rootVisual.TransformToDescendant( this ) != null ) { pixelOffset = rootVisual.TransformToDescendant( this ).Transform( pixelOffset ); } } return pixelOffset; } private Point TryApplyVisualTransform( Point point, Visual v, bool inverse, bool throwOnError, out bool success ) { success = true; if( v != null ) { Matrix visualTransform = GetVisualTransform( v ); if( inverse ) { if( !throwOnError && !visualTransform.HasInverse ) { success = false; return new Point( 0, 0 ); } visualTransform.Invert(); } point = visualTransform.Transform( point ); } return point; } private Point ApplyVisualTransform( Point point, Visual v, bool inverse ) { bool success = true; return TryApplyVisualTransform( point, v, inverse, true, out success ); } private Matrix GetVisualTransform( Visual v ) { if( v != null ) { Matrix m = Matrix.Identity; Transform transform = VisualTreeHelper.GetTransform( v ); if( transform != null ) { Matrix cm = transform.Value; m = Matrix.Multiply( m, cm ); } Vector offset = VisualTreeHelper.GetOffset( v ); m.Translate( offset.X, offset.Y ); return m; } return Matrix.Identity; } }
Visual Studio Installer 編集の自動化
Visual Studio Installer - kitunechan’s blog の続き
毎回Orcaを使って編集するのはめんどくさいし、”よく忘れます”← ここ重要
コマンドラインやバッチファイル的なものはないかと探したらありました。
vbsを使う方法が一番簡単そうです。
MSIファイルのWindows Installerデータベースをプログラムで編集する: .NET Tips: C#, VB.NET
以下の内容でvbsファイルを作成します。
[アドバタイズショートカットから通常のショートカットに切り替える]
NormalShortCut.vbs
Option Explicit Const msiOpenDatabaseModeTransact = 1 Dim msiPath : msiPath = Wscript.Arguments(0) Dim installer Set installer = Wscript.CreateObject("WindowsInstaller.Installer") Dim database Set database = installer.OpenDatabase(msiPath, msiOpenDatabaseModeTransact) Dim query query = "INSERT INTO Property(Property, Value) VALUES('DISABLEADVTSHORTCUTS', '1')" Dim view Set view = database.OpenView(query) view.Execute database.Commit
あとはこのファイルにmsiファイルを渡すだけです。
何かのウインドウが開くこともなく、msiファイルが書き換えられます。
Visual StudioでPostBuildEventに以下のコードを入力して自動化します。
"[パス]\NormalShortCut.vbs" "$(BuiltOuputPath)"
コードだけでlog4netを使う
ログ出力のライブラリとしてlog4netやらNLogが人気があります。
紹介しているサイトを見るとすぐXMLの設定を書かせたがるのですが、後から変更するのか?と。
ということで、log4netをコードから設定する方法です。
ログをファイル出力します。
// 大体一つを使いまわすので static readonly で作る // GetLogger内の文字列は好きなのでいい public static readonly log4net.ILog logger = log4net.LogManager.GetLogger( "Log" ); // コンストラクタ的なところで設定を記述する public App() { // 出力先のAppenderを作成します // ファイル出力で、日付やファイルサイズで新規ファイルを作成するタイプ var Appender = new log4net.Appender.RollingFileAppender() { // 出力するファイル名 File = "log.log", // ファイル追記モード (Falseだと上書き) AppendToFile = true, // ログファイル名を固定にする StaticLogFileName = true, // ログファイル名のローテーション番号の順番 CountDirection = 1, // ログファイルの最大世代 MaxSizeRollBackups = 1, // 文字コード指定 Encoding = System.Text.Encoding.UTF8, // ログを新規ファイルに切替える条件 RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Size, // 最大ファイルサイズ MaxFileSize = 3072000, //ログのフォーマット Layout = new log4net.Layout.PatternLayout(@"%d [%t] %-5p %type - %m%n"), }; // 記述した設定を有効にする 忘れないように! Appender.ActivateOptions(); var log = (log4net.Repository.Hierarchy.Logger)logger.Logger ; // 出力レベルの設定 デフォルトはALLなのでコメントアウトしちゃう //log.Level = log4net.Core.Level.All; // 出力先を追加します log.AddAppender( Appender ); // 設定を有効にする 忘れないように! log.Hierarchy.Configured = true; }
後は好きなところでメッセージ出力して下さい。
logger.Debug( "Debug" ); logger.Info( "Info" ); logger.Warn( "Warn" ); logger.Error( "Error" ); logger.Fatal( "Fatal" );
参考:
log4net の Appender を プログラム上で動的に 生成、追加 する 方法
c# - How to configure NHibernate logging with log4net in code, not in xml file? - Stack Overflow
Log4Net設定 MAXファイルサイズ及び日単位で切替え | Wait Cursor
Log4net(6) RollingFileAppender 意味不明パラメータ:CountDirection - shima111の日記
C# .Net アプリケーションで複数のアイコンを登録する方法
C# .Net のアプリケーションでショートカットアイコン用に複数のアイコンファイルをEXEに入れたかったのですが、
Visual Studio 2013だけではできませんでした。
参考イメージ
ショートカット アイコンの変更 - Google 検索
では本題です。
長いので目次付けました。
- そもそもアイコンを設定する方法
- Win32リソースを作成して複数のアイコンを登録する方法
- Resource Hackerを使って後から複数のアイコンを追加する方法
- InsertIconsを使って後から複数のアイコンを追加する方法
おすすめは最後の「InsertIconsを使って後から複数のアイコンを追加する方法」です。
続きを読むVisual Studio Installer Projects
これのお話
Microsoft Visual Studio 2015 Installer Projects - Visual Studio Marketplace
ほぼ覚書
UpgradeCode を一致させておくとインストールプログラム一覧にダブらずにインストールできる。
ProductCode は必ず変更すること。
ファイルのPermanentをTrueにしておくとアンインストール時にも削除されない。
インストーラーのバージョンは作成時はどうあがいても3桁(1.0.0)でしかビルドできない。
ビルド後にOrcaを使えば変更可能。
定義されているプロパティ一覧
Property Reference - Windows applications | Microsoft Docs
[WindowsVolume]や[ProductVersion]とか
アイコンはそれぞれ設定する必要がある
・ショートカットファイル
・インストールプログラム一覧
作成されるショートカットがアドバタイズショートカットというタイプなので通常のショートカットに切り替える必要がある。
OrcaでMSIファイルを開き、PropertyテーブルにProperty「DISABLEADVTSHORTCUTS」、Value「1」の行を新たに追加する。
アドバタイズショートカットではなく、普通のショートカットを作成する - .NET Tips (VB.NET,C#...)
Orcaは以下の場所にインストーラーが保存されている
C:\Program Files\Microsoft SDKs\Windows\[バージョン]\Bin\Orca.exe
Program Files (x86)ではないので注意。まあ、両方見よう
WPFで画像処理中にメモリが開放されず・・・
覚書程度に記載です
WPFで Canvas(正確にはVisualクラス?) を RenderTargetBitmap を使って大量に画像出力していたら
エラー「メモリが足りません」
System.OutOfMemoryException: プログラムの実行を続行するための十分なメモリがありませんでした。
はー悲しい。。。
こんなのループしました。
※ブログ用に書いたから動かないかも
// 画像の読み込み var bmp = new BitmapImage(new Uri( "ファイル名をいれてね" )); // 画像拡大率の指定 var Scale = 1.5D; for ( var i = 0 ; i < 1000; i++){ var canvas = new Canvas() { SnapsToDevicePixels = true, Children = { ( new Func<UIElement>( ()=>{ var re = new Image() { Source = bmp, LayoutTransform = new ScaleTransform( Scale, Scale )//画像拡大 }; RenderOptions.SetBitmapScalingMode( re, BitmapScalingMode.HighQuality); return re; }))(), }, }; canvas.Measure( new System.Windows.Size( bmp.Width * Scale, bmp.Height * Scale ) ); canvas.Arrange( new Rect( 0, 0, bmp.Width * Scale, bmp.Height * Scale ) ); canvas.UpdateLayout(); // ファイル書き出し var r = new RenderTargetBitmap( (int)canvas.ActualWidth, (int)canvas.ActualHeight, 96, 96, PixelFormats.Default ); r.Render( canvas ); var img = new PngBitmapEncoder() { Frames = { BitmapFrame.Create( r ) }, }; using( var stream = new FileStream( "./"+i+".png", FileMode.Create ) ) { img.Save( stream ); } // 強制メモリ解放!!! GC働けええええ // GC.Collect(); // GC.WaitForPendingFinalizers(); // GC.Collect(); }
メソッドを抜けるまでガンガンメモリが増えていくやつです。
メモリリークしているわけじゃなくてGC君が「後でやるよ」をし続けた結果「メモリが足りません」の流れっぽいです。
ただし GC.Collect(); だけでは足りないみたいで GC.WaitForPendingFinalizers(); の必要がありました。
こんな感じ?
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();
Visual Studio 再起動の拡張機能の紹介
インテリセンスのウインドウが表示されなくなるとか、
Xamlでデザインいじってる時にGUIからプロパティが操作効かなくなったり、
と割とよく不具合があるので再起動しまくりです。
[×] 押して毎回起動するのめんどくせーーーーーと思ってたらありました。
Visual Studio Restart
visualstudiogallery.msdn.microsoft.com
通常起動と管理者で起動があるのでご注意下さい。
あと、Undoの情報も消えちゃうよね。
Visual Studio 許すまじ
Visual Studio 2013 にて
Showで作成した小ウインドウを閉じた時に親ウインドウが見えなくなってしまうという不思議な現象に見舞われました・・・
どうも原因は親ウインドウよりもVisual Studioがしゃしゃり出てきて前面に表示されるため、
親ウインドウはアクティブ状態だけど表示されてない感じになってました。
Visual Studio再起動したら直りました。
調子悪い時はVisual Studio再起動しよ。。。
続きを読む定義済みコマンドを使っていこう
既に定義がされているコマンドがいくつかあります。
- http://msdn.microsoft.com/ja-jp/library/system.windows.input.applicationcommands.aspx
Open や Save など一般的なアプリケーションで有用なコマンドを持つ
- ComponentCommands
ちょっと特殊。あまり使う機会はないんじゃなかろうか
- MediaCommands
Play や Stop など音楽や動画を扱うときに便利なコマンドを持つ
- NavigationCommands
ブラウザに類似したコマンドを持つ
- EditingCommands
エディタでよく使われるコマンドを持つ
ショートカットキーが定義されていたり、Copy、Pasteなどに至っては各コントロールにより処理が入っていたりして便利です。
これを使わない手はない!と思ったのですが、手順がわかるまでが大変でした。
処理の追加
Windowクラス(UIElementなら何でもOK)のCommandBindingsで処理を追加します。
※ 方法がいくつかあるので一つの手段として見て下さい。
public partial class MainWindow: Window { public MainWindow() { InitializeComponent(); this.CommandBindings.Add( new CommandBinding( ApplicationCommands.New, New, CanNew ) ); } // 実行されるメソッド public void New( object sender, ExecutedRoutedEventArgs e ){ MessageBox.Show( "New" ); } // こんなのでもおっけー //public ExecutedRoutedEventHandler New { // get { // return ( s, e ) => { // MessageBox.Show( "New" ); // }; // } //} public void CanNew ( object sender, CanExecuteRoutedEventArgs e ){ // 実行できるなら e.CanExecute = true; // 実行できない(グレー表示)なら e.CanExecute = false; e.CanExecute = true; } }
この記述で
<Menu DockPanel.Dock="Top" Margin="0,0,0,1"> <MenuItem Header="ファイル(_F)"> <MenuItem Command="New"/> </MenuItem> </Menu>
文字もショートカットキーも出てる!!便利ー!!
ショートカットキーの追加
Newコマンドでは Ctrl+N が初めから定義されていましたが、名前を付けて保存 の SaveAs にはショートカットキーが割り当てられていませんでした。
自分で割り当てろってことですね。
ショートカットキーを割り当てて、Ctrl+Shift+S の表示もさせるにはデザインが出来上がる前に記述する必要がありました。
つまり ApplicationCommands.SaveAs に Ctrl+Shift+S を割り当てるには InitializeComponent より前に書いてしまうか、
public MainWindow() { ApplicationCommands.SaveAs.InputGestures.Add( new KeyGesture( Key.S, ModifierKeys.Control | ModifierKeys.Shift ) ); InitializeComponent(); }
静的コンストラクタ(Static)に追加して最速で呼び出します。
static MainWindow() { ApplicationCommands.SaveAs.InputGestures.Add( new KeyGesture( Key.S, ModifierKeys.Control | ModifierKeys.Shift ) ); }
全体像はこんな感じになりました。
public partial class MainWindow: Window { public MainWindow() { InitializeComponent(); this.CommandBindings.Add( new CommandBinding( ApplicationCommands.New, New, CanNew ) ); // SaveAs はラムダ式で強引に書いちゃう this.CommandBindings.Add( new CommandBinding( ApplicationCommands.SaveAs, ( s, e ) => { MessageBox.Show( "SaveAs" ); } ) ); } static MainWindow() { ApplicationCommands.SaveAs.InputGestures.Add( new KeyGesture( Key.S, ModifierKeys.Control | ModifierKeys.Shift ) ); } // 実行されるメソッド public void New( object sender, ExecutedRoutedEventArgs e ) { MessageBox.Show( "New" ); } // こんなのでもおっけー //public ExecutedRoutedEventHandler New { // get { // return ( s, e ) => { // MessageBox.Show( "New" ); // }; // } //} public void CanNew( object sender, CanExecuteRoutedEventArgs e ) { // 実行できるなら e.CanExecute = true; // 実行できない(グレー表示)なら e.CanExecute = false; e.CanExecute = true; } }
Xaml部
<Window x:Class="LivetWPFApplication1.Views.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <DockPanel> <Menu DockPanel.Dock="Top"> <MenuItem Header="ファイル"> <MenuItem Command="New"/> <MenuItem Command="SaveAs"/> </MenuItem> </Menu> <DockPanel></DockPanel> </DockPanel> </Window>
やったぜ!
参考:http://blogs.wankuma.com/kazuki/archive/2008/03/20/128720.aspx
自分でコントロールを描画する
ControlPaint
ControlPaint クラス (System.Windows.Forms)
自分でコントロールを描画したい時に便利なクラスです
参考:コントロールを描画する: .NET Tips: C#, VB.NET
Button周りのボーダーグラデーションも指定色から作成することができます。
ControlPaint.LightLight( Color );
ControlPaint.Light( Color );
ControlPaint.Dark( Color );
ControlPaint.DarkDark( Color );
上から順に
・強い光色
・弱い光色
・弱い影
・強い影
となっています。
DarkDarkとか安直すぎない(*_*)
ウインドウの位置を保存・復元する
プログラムを再度起動した時に前回のウインドウ位置やサイズを保存・復元する方法です。
保存する情報は
・ウインドウのサイズ(Width,Height)
・ウインドウの位置(Location)
・ウインドウの状態(最大化、最小化)
になります。
また、起動した時に画面からはみ出ている状態では困るのでそこも修正します。
ウインドウの情報を保存する
ウインドウのサイズ、位置、状態のやりとりはWinAPIを使用したほうが便利です。
画面外に吹っ飛ぶようなウインドウでも、画面内に収まるように自動で修正してくれます。
こんな感じのクラスを作って
public class WinAPI { // WinAPI // ウインドウ情報をセットする [DllImport("user32.dll")] public static extern bool SetWindowPlacement( IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl ); //WinAPI // ウインドウ情報を取得する [DllImport("user32.dll")] public static extern bool GetWindowPlacement( IntPtr hWnd, out WINDOWPLACEMENT lpwndpl ); // 以下 WinAPIで使用するクラス定義 public struct WINDOWPLACEMENT { public int length; public int flags; public SW showCmd; public POINT minPosition; public POINT maxPosition; public RECT normalPosition; } [StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; public POINT( int x, int y ) { this.X = x; this.Y = y; } } [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left; public int Top; public int Right; public int Bottom; public RECT( int left, int top, int right, int bottom ) { this.Left = left; this.Top = top; this.Right = right; this.Bottom = bottom; } } public enum SW { HIDE = 0, SHOWNORMAL = 1, SHOWMINIMIZED = 2, SHOWMAXIMIZED = 3, SHOWNOACTIVATE = 4, SHOW = 5, MINIMIZE = 6, SHOWMINNOACTIVE = 7, SHOWNA = 8, RESTORE = 9, SHOWDEFAULT = 10, } }
こんな感じで使ってます。
public void ウインドウ情報の取得( Control c ) { WINDOWPLACEMENT wp = new WINDOWPLACEMENT(); WINAPI.GetWindowPlacement(c.Handle, out wp); } public void ウインドウ情報を設定( WINDOWPLACEMENT wp ) { if( wp.length != 0 ) { wp.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT)); wp.flags = 0; wp.showCmd = ( wp.showCmd == SW.SHOWMINIMIZED ? SW.SHOWNORMAL : wp.showCmd ); IntPtr hwnd = c.Handle; WinAPI.SetWindowPlacement(hwnd, ref wp); } }
リフレクション
最近WPFを触っています。
が、ほしいフィールドやプロパティがプライベートだったりして歯がゆい思いをよくしてます。
気に入らないのでリフレクションで持ってきてしまいましょう。
.NET FOLLOWER » C#: How to set or get value of a private or internal property through the Reflection
リフレクションに便利な拡張メソッド
public static class ReflectionHelper { private static PropertyInfo GetPropertyInfo(Type type, string propertyName) { PropertyInfo propInfo = null; do { propInfo = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); type = type.BaseType; } while (propInfo == null && type != null); return propInfo; } public static object GetPropertyValue(this object obj, string propertyName) { if (obj == null) throw new ArgumentNullException("obj"); Type objType = obj.GetType(); PropertyInfo propInfo = GetPropertyInfo(objType, propertyName); if (propInfo == null) throw new ArgumentOutOfRangeException("propertyName", string.Format("Couldn't find property {0} in type {1}", propertyName, objType.FullName)); return propInfo.GetValue(obj, null); } public static void SetPropertyValue(this object obj, string propertyName, object val) { if (obj == null) throw new ArgumentNullException("obj"); Type objType = obj.GetType(); PropertyInfo propInfo = GetPropertyInfo(objType, propertyName); if (propInfo == null) throw new ArgumentOutOfRangeException("propertyName", string.Format("Couldn't find property {0} in type {1}", propertyName, objType.FullName)); propInfo.SetValue(obj, val, null); } }
Blend for Visual Studio のデザイン表示について
WPFのデザイン(Xaml)表示がされなくなってしまう事がありました。
調べた結果、Blend for Visual Studio 2013 で .NET Framework 4.5 より上のバージョンを使用するとデザイン表示ができないようです。
.NET Framework 4.5.1
.NET Framework 4.5.2
.NET Framework 4.6
このバージョンを使うときは Blend for Visual Studio 2015 を使いましょう!