カテゴリ:.Net開発( 216 )
(.Net)フォルダ削除のDirectory.Delete(path, true)にやられた

.Netの System.IO.Directory.Delete(path, true) を使ってサブフォルダ・ファイルごと任意のディレクトリを削除しようとしたのですが、"アクセスが拒否されました" とIOException例外になります。


??ってなってたら、サブフォルダの一つが読み取り専用属性がついていました。
Directory.Deleteで削除しようとする場合、読み取り専用属性がついてると削除させてくれないようです。

対処法としては、一旦サブフォルダの読み取り専用属性を全てのけてから、Directory.Deleteメソッドを実行するしかなさそうですorz...
(VB.NET用のMy.Computer.FileSystem.DeleteDirectoryメソッドを使うと一気に消せるようですが。。)

読み取り専用ファイルがあるときでもフォルダを削除する: DOBON.NET Tips: C#, VB.NETに、一旦読み取り専用属性のけてから削除するサンプルコードがあるので、これを使わせてもらいました。

今まで.Netそこそこは触ってきたはずなのに、未だにこんな仕様で驚いている3流PGでした。。。
[PR]
by jehoshaphat | 2014-02-15 01:06 | .Net開発 | Trackback | Comments(0)
(.Net)任意のプロセスのCPU使用率を出すコード
任意のプロセスのCPU使用率を求めたいと思ってググっていたらVisualStudioフォーラム:複数のプロセス毎のCPU使用率同時取得にドンピシャな答えがありました。

コピペになりますが、ベンチマークソフト SUPER_PI のCPU使用率を求めたい時はこうなります。
mSystemProcessNameで、プロセスイメージ名(拡張子は無し)を指定します。

このPGでは指定したプロセスのCPU使用率しか求めれないため、全プロセスのCPU使用率を知りたい時は、一旦全プロセスイメージ名を取得して、それぞれ別スレッドでこのコードを動かすみたいな作りになろうかと思います。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;
 
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
bool mStop = false;
string mSystemProcessName = "SUPER_PI";
int mMonitorInterval = 1;
 
/// <summary>パフォーマンスカウンタ・カテゴリ名(プロセス)</summary>
const string CATEGORY_NAME = "Process";
/// <summary>パフォーマンスカウンタ・カテゴリ名(プロセッサ)</summary>
const string CATEGORY_NAME_PROCESSOR = "Processor";
/// <summary>パフォーマンスカウンタ・カウンタ名(プロセッサ使用時間)</summary>
const string COUNTER_NAME_PROCESSOR_TIME = "% Processor Time";
/// <summary>ナノ秒単位での1秒の値</summary>
double NANO_SECOND_TICKS = 10000000;
/// <summary>CPUのコア数</summary>
double mCoreNumber;
 
#region 指定時間間隔内でのデータ格納領域
/// <summary>プロセッサ使用時間</summary>
double[] mDataContainer_ProcessorPercent= new double[10];
#endregion
 
#region 直前の監視タイミングでのデータ格納領域
/// <summary>プロセッサ使用時間</summary>
double mBefor_ProcesserPercent=0;
#endregion
 
PerformanceCounter mProcessTime;
 
#region CPUのコア数取得
PerformanceCounter mWorkCounter_CoreNumber;
 
mCoreNumber = 1;
if (PerformanceCounterCategory.Exists(CATEGORY_NAME_PROCESSOR))
{
if (PerformanceCounterCategory.CounterExists(COUNTER_NAME_PROCESSOR_TIME, CATEGORY_NAME_PROCESSOR))
{
for (int i = 0; i < 32; i++)
{
try
{
string wkInst = i.ToString();
mWorkCounter_CoreNumber = new PerformanceCounter(CATEGORY_NAME_PROCESSOR, COUNTER_NAME_PROCESSOR_TIME, wkInst, ".");
long buf = mWorkCounter_CoreNumber.RawValue;
mCoreNumber = double.Parse((i + 1).ToString());
}
catch
{
break;
}
}
}
}
#endregion
 
#region プロセスのパフォーマンスカウンタ定義
 
 
// プロセッサ使用時間
mProcessTime = new PerformanceCounter(
CATEGORY_NAME,
COUNTER_NAME_PROCESSOR_TIME,
mSystemProcessName,
".");
#endregion
 
// カレント時間を取得
DateTime current = DateTime.Now;
// ループ
while (true)
{
// ストップフラグが設定されたらループエンド
if (mStop) break;
// カレント時間の秒とが現在の秒と異なるか?
if (DateTime.Now.Second != current.Second)
{
// カレント時間の更新
current = DateTime.Now;
// カレント秒をログ出力間隔で割る
int div_sec = current.Second % mMonitorInterval;
// カレント秒がログ出力間隔で割り切れるか?
if (div_sec == 0)
{
//------------------------------------------------
// 監視間隔が切り替わるタイミングでログ情報出力
//------------------------------------------------
//PutLog(current); // ログファイルへデータ登録
//InitWorkArea(); // データ格納クラスの初期化
}
// CPU使用率 CPU使用時間(単位:100ナノ秒) / 1秒(10000000*100ナノ秒)/ CPUのコア数
mDataContainer_ProcessorPercent[div_sec] = (mProcessTime.RawValue - mBefor_ProcesserPercent) / NANO_SECOND_TICKS / mCoreNumber;
//CPU使用率出力
Debug.WriteLine(mDataContainer_ProcessorPercent[div_sec]);
// 直前の値として退避
mBefor_ProcesserPercent = mProcessTime.RawValue;
}
Thread.Sleep(10);
}
}
}
}


参考:
プロセス毎のCPU使用率の取得: DOBON.NETプログラミング掲示板過去ログ
CPU使用率(C#/VB.NET) [サンプルソース] [ヨーキー景吾の逃走]
C++でCPU使用率やメモリ使用量を調べる - 小さな星がほらひとつ C++の場合はこの方法でプロセスのCPU使用率も取れるようです。
[PR]
by jehoshaphat | 2014-02-12 00:53 | .Net開発 | Trackback | Comments(0)
(.Net)ターミナルサーバでユーザのプロセスのメモリ使用量を取りたい。
WindowsServer2003上でターミナルサーバを運用してますが、どうやらメモリを使いすぎているユーザがいるようです。
それで、数日間どのユーザがどのプロセスでメモリを使いすぎているのか経過調査を行うことにしました。


当初はパフォーマンスログで取ろうかなと思っていたんですが、パフォーマンスログではそのプロセスを使っているのがどのユーザなのかがわかりません。

仕方が無いので .Net Framework で現在のプロセス情報を取得し、ユーザと紐付けてCSVに落とすアプリケーションを作ることにしました。
それをOSのタスクスケジューラーに仕込んで、一定間隔で動かす運用です。

CSVには全プロセスの情報と、ユーザ単位でメモリ使用量を集計した情報を別々に出力します。

現在動いているプロセスの情報は System.Diagnostics.Process.GetProcesses() で取れるんですが、そのプロセスがどのユーザがオーナーとなっているかがわかりません。
ココは(.Net)現在のユーザが起動した特定のプロセスを終了するで書いた WMI を使った方法を取ることにしました。


コードしては以下のような感じです。文字数制限のためハイライトはOFFです。(C# .NetFramework2.0)

try{
//ユーザとプロセスIDを取得。(WMI使用)
ManagementScope scope = new ManagementScope("\\\\.\\ROOT\\CIMV2");

//プロセス情報取得
System.Diagnostics.Process[] ps = System.Diagnostics.Process.GetProcesses();

scope.Connect();
ObjectQuery query = new ObjectQuery(@"SELECT * FROM Win32_Process");
ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
ManagementObjectCollection col = searcher.Get();

//プロセスIDとユーザ名のハッシュテーブル[プロセスID,ユーザ名]
Hashtable htPrcUsrMap = new Hashtable();
foreach (ManagementObject o in col){
string pid = o["ProcessId"].ToString();

Object[] UserInfo = new object[2];
//見つからない時があるので、try - catch
try{
o.InvokeMethod("GetOwner", UserInfo);
}catch (Exception){
UserInfo[0] = "unkwon";
}

string UserName = (string)UserInfo[0]; //実行ユーザ名称取得
//string DomainName = (string)UserInfo[1]; //実行ユーザのドメイン名称取得

//配列にプロセスIDとユーザ名格納
htPrcUsrMap.Add(pid, UserName);
}

//現在日時取得
DateTime dt = DateTime.Now;
//データを格納するためのテーブルインスタンス作成
DataSet1.tblProcessDataDataTable tbl = new DataSet1.tblProcessDataDataTable();

//出力ファイル名
string strExportFile_Process = @"C:\PerfLogs\processCounter_Process.csv";
string strExportFile_User = @"C:\PerfLogs\processCounter_User.csv";

StringBuilder strBld_Process = new StringBuilder();

foreach (System.Diagnostics.Process p in ps){
try{
DataSet1.tblProcessDataRow row = tbl.NewtblProcessDataRow();

//タイムスタンプ出力(日時分)
string timestamp = dt.ToString("yyyy-MM-dd HH:mm:00");
strBld_Process.Append(timestamp);
strBld_Process.Append(",");
row.timestamp = timestamp;

//ユーザ名出力
string user = (string)htPrcUsrMap[p.Id.ToString()];
strBld_Process.Append(user);
strBld_Process.Append(",");
row.user = user;

//プロセスID
string processId = p.Id.ToString();
strBld_Process.Append(processId);
strBld_Process.Append(",");
row.process_id = processId;

//プロセス名
string processName = p.ProcessName;
strBld_Process.Append(processName);
strBld_Process.Append(",");
row.process_name = processName;

//CPU使用率(未実装。とりあえず0にしておく)
double cpuPercent = 0;
strBld_Process.Append(cpuPercent);
strBld_Process.Append(",");
row.cpu = cpuPercent;

//CPU時間
TimeSpan cpuTime = p.TotalProcessorTime;
strBld_Process.Append(cpuTime.TotalSeconds);
strBld_Process.Append(",");
row.cpu_time = (decimal)cpuTime.TotalSeconds;

//workingset(物理メモリ使用量)
long workingset = p.WorkingSet64;
strBld_Process.Append(workingset);
strBld_Process.Append(",");
row.workingset = workingset;

//PrivateMemory(物理メモリ+スワップ使用量)
long privateMemory = p.PrivateMemorySize64;
strBld_Process.Append(privateMemory);
strBld_Process.Append(",");
row.privatememorysize = privateMemory;

//最大workingset
long perkworkingset = p.PeakWorkingSet64;
strBld_Process.Append(perkworkingset);
strBld_Process.Append(",");
row.peak_workingset = perkworkingset;

//プロセスパス
string processPath = p.MainModule.FileName;
strBld_Process.Append(processPath);
strBld_Process.Append(",");
row.process_path = processPath;

/*
//メインウィンドウキャプション(実行ユーザでしか出ない)
string windowCaption = p.MainWindowTitle;
strBld_Process.Append(windowCaption);
strBld_Process.Append(",");
*/

strBld_Process.Append(Environment.NewLine);
tbl.AddtblProcessDataRow(row);
}catch (Exception ex){
Console.WriteLine("エラー: {0}", ex.Message);
strBld_Process.Append(Environment.NewLine);
}
}

//ユーザ毎の統計データ生成
//重複を除去するため DataView を使う
DataView vw = new DataView(tbl);
//重複除去を第二引数に指定。第三引数で一意とすべき列を指定。(複数列でも可能)
DataTable tblRes = vw.ToTable("DistinctTable", true, new string[] { "user" });
//合計の列を追加
tblRes.Columns.Add("sum_cpu", Type.GetType("System.Double"));
tblRes.Columns.Add("sum_workingset", Type.GetType("System.Int64"));
tblRes.Columns.Add("sum_privatememorysize", Type.GetType("System.Int64"));
tblRes.Columns.Add("sum_processcount", Type.GetType("System.Int64"));
tblRes.Columns.Add("sum_cputime", Type.GetType("System.Decimal"));

//重複除いたDataTableをループし、元のDataTableから集計値を求める
foreach (DataRow row in tblRes.Rows) {
row["sum_cpu"] = tbl.Compute("SUM(cpu)", "user = '" + row["user"] + "'");
row["sum_cputime"] = tbl.Compute("SUM(cpu_time)", "user = '" + row["user"] + "'");
row["sum_workingset"] = tbl.Compute("SUM(workingset)", "user = '" + row["user"] + "'");
row["sum_privatememorysize"] = tbl.Compute("SUM(privatememorysize)", "user = '" + row["user"] + "'");
row["sum_processcount"] = tbl.Compute("Count(user)", "user = '" + row["user"] + "'");
}

//sum_workingset の降順でソートをかける
DataRow[] srtRows = (DataRow[])tblRes.Select("" , "sum_workingset DESC").Clone();
DataTable tblSrt = new DataTable();
tblSrt = tblRes.Clone();
foreach (DataRow row in srtRows){
tblSrt.ImportRow(row);
}

//テキスト生成
StringBuilder strBld_User = new StringBuilder();
strBld_User = new StringBuilder();
foreach (DataRow row in tblSrt.Rows){
strBld_User.Append(dt.ToString("yyyy-MM-dd HH:mm:00"));
strBld_User.Append(",");
strBld_User.Append(row["user"]);
strBld_User.Append(",");
strBld_User.Append(row["sum_cpu"]);
strBld_User.Append(",");
strBld_User.Append(row["sum_cputime"]);
strBld_User.Append(",");
strBld_User.Append(row["sum_workingset"]);
strBld_User.Append(",");
strBld_User.Append(row["sum_privatememorysize"]);
strBld_User.Append(",");
strBld_User.Append(row["sum_processcount"]);
strBld_User.Append(",");
strBld_User.Append(Environment.NewLine);
}

//ファイル出力
System.Text.Encoding enc = System.Text.Encoding.GetEncoding("shift_jis");
if (!File.Exists(strExportFile_Process)){
string strHead = "日時,ユーザ,プロセスID,プロセス名,CPU使用率(未実装)"
+ ",CPU時間,物理メモリ使用量,物理+スワップ使用量,最大物理メモリ使用量,プロセスパス";//,ウィンドウ名";
File.AppendAllText(strExportFile_Process, strHead + Environment.NewLine, enc);
}
if (!File.Exists(strExportFile_User)){
string strHead = "日時,ユーザ,CPU使用率合計(未実装)"
+ ",CPU時間合計(秒),物理メモリ使用量合計,物理+スワップ使用量合計,プロセス数";
File.AppendAllText(strExportFile_User, strHead + Environment.NewLine, enc);
}
File.AppendAllText(strExportFile_Process, strBld_Process.ToString(), enc);
File.AppendAllText(strExportFile_User, strBld_User.ToString(), enc);

}catch (Exception ex){
File.AppendAllText(@"C:\PerfLogs\err.txt" , DateTime.Now.ToString() + " " + ex.Message + " Trace:" + ex.StackTrace + Environment.NewLine);

}



ユーザの統計に使う DataTable は以下のような構成にしてます。

データセット名:DataSet
テーブル名:tblProcessData
列:timestamp :System.String
列:user :System.String
列:process_id :System.String
列:process_name :System.String
列:cpu :System.Double
列:workingset :System.Int64
列:privatememorysize :System.Int64
列:peak_workingset :System.Int64
列:process_path :System.String
列:cpu_time :System.Decimal

実際に動かすと、WMIを使ってプロセスとユーザ情報を取得する部分が非常に時間がかかります。
60ユーザくらいで800-900くらいのプロセスが動いている状態で、数分はかかります。
また、この部分、CPUも結構食うようで1コア専有しちゃうんですよね。

WMI以外にユーザとプロセスの情報を取る方法が知りたいと思う今日この頃です。

参考:
MSDN:Process メンバ (System.Diagnostics)
[VB / C#] 実行中プロセスの各種情報を取得
【C#】プロセス実行ユーザ名称の取得API - Insider.NET - @IT
[PR]
by jehoshaphat | 2014-02-11 00:46 | .Net開発 | Trackback | Comments(0)
(.NET)ClickOnceで発行後にアプリを起動すると「配置IDがサブスクリプションと一致しません」と怒られる
ClickOnceでアプリケーションを配置してるんですが、バグを修正し新バージョンを発行しなおしました。
それでクライアント側のショートカットからアプリケーションを起動しようとすると、更新チェック後にエラーが発生し起動できません。
(ちなみに、アプリケーションのショートカットからでなく、ブラウザから起動すると別物してインストールされてしまいます。何故かアプリケーション名に -1 が付きます)

エラーの詳細を見たところ内容は以下のような感じでした。

エラー
プラットフォームのバージョン情報
Windows : 6.1.7601.65536 (Win32NT)
Common Language Runtime : 4.0.30319.18408
System.Deployment.dll : 4.0.30319.18408 built by: FX451RTMGREL
clr.dll : 4.0.30319.18408 built by: FX451RTMGREL
dfdll.dll : 4.0.30319.18408 built by: FX451RTMGREL
dfshim.dll : 4.0.31106.0 (Main.031106-0000)

ソース
配置の URL: file:///C:/Users/hoge/AppData/Roaming/Microsoft/Windows/Start%20Menu/Programs/testapp/hogehogeapp.appref-ms%7C
サーバー: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
配置プロバイダの URL: http://xxx.xxx.xxx.xxx:/testapp/hogehogeapp.application

エラーの概要
以下はエラーの概要です。これらのエラーの詳細はログに一覧表示されています。
* C:\Users\hoge\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\testapp\hogehogeapp.appref-ms| のライセンス認証により例外が発生しました。 次の失敗メッセージが検出されました:
+ 配置 ID がサブスクリプションと一致しません。

コンポーネント ストア トランザクションの失敗の概要
トランザクション エラーは検出されませんでした。

警告
この操作中に警告は発生しませんでした。

操作の進行状況
* [2014/01/24 16:33:32] : C:\Users\hoge\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\testapp\hogehogeapp.appref-ms| のライセンス認証が開始されました。
* [2014/01/24 16:33:32] : 配置で指定されたように必要な更新チェックを実行しています。

エラーの詳細
この操作中に次のエラーが検出されました。
* [2014/01/24 16:33:32] System.Deployment.Application.DeploymentException (SubscriptionState)
- 配置 ID がサブスクリプションと一致しません。
- ソース: System.Deployment
- スタック トレース:
場所 System.Deployment.Application.SubscriptionStore.CheckUpdateInManifest(SubscriptionState subState, Uri updateCodebaseUri, AssemblyManifest deployment, Version currentVersion, Boolean& bUpdateInPKTGroup)
場所 System.Deployment.Application.ApplicationActivator.PerformDeploymentUpdate(SubscriptionState& subState, String& errorPageUrl)
場所 System.Deployment.Application.ApplicationActivator.ProcessOrFollowShortcut(String shortcutFile, String& errorPageUrl, TempFile& deployFile)
場所 System.Deployment.Application.ApplicationActivator.PerformDeploymentActivation(Uri activationUri, Boolean isShortcut, String textualSubId, String deploymentProviderUrlFromExtension, BrowserSettings browserSettings, String& errorPageUrl)
場所 System.Deployment.Application.ApplicationActivator.ActivateDeploymentWorker(Object state)

コンポーネント ストア トランザクションの詳細
トランザクション情報はありません。


エラーは「ライセンス認証により例外が発生しました」となり、原因が「配置 ID がサブスクリプションと一致しません。」となっています。

ClickOnce用にしている証明書が変更されたりすると、別アプリと認識され、このようなエラーが出ることは知っていましたが、今回は証明書は変更してません。

いろいろ調べてみるとMSDN:.NET Framework 2.0 ベースのアプリケーションでClickOnce の証明書を変更すると別のアプリケーションとして認識されるに気になる記述が。。。


.NET Framework 2.0 は以下の 4 つの項目を基に ClickOnce アプリケーションの一意性を識別します。.NET Framework は 各項目の 1 つでも異なる場合には別アプリケーションとして識別し、新規インストールを実施します。

アプリケーション名 <name>
公開キー トークン <publicKeyToken>
カルチャ <language>
プロセッサアーキテクチャ (x86 など) <processorArchitecture>

(.NET Framework 2.0 SP1 が当たっていると、アプリケーション名、カルチャ、プロセッサアーキテクチャの項目でアプリケーションを識別するようです。)

ここでピンと来たのがプロセッサアーキテクチャです。
そういえば、ビルドがうまくいかくて、VisutalStudio でプロセッサアーキテクチャを "x86" から "ANY CPU" に変えてました。

この問題のアプリケーションのクライアント側のClickOnceのショートカットをテキストエディタで見てみると、以下のようになっていました。

http://xxx.xxx.xxx.xxx:/testapp/hogehogeapp.application, Culture=neutral, PublicKeyToken=4fdc3b94a306e3c2, processorArchitecture=x86

確かにまともに動いていた時はプロセッサアーキテクチャはx86だったようです。

VS側で "ANY CPU" を "x86" に戻してビルドしなおし発行したところ、今度はエラーが出ず最新版に置き換わりました。

まったくヤレヤレです。
ClickOnceは確かに手軽にクライアントへの展開ができるから便利なんですが、アップデート絡みになるといろいろかゆいところに手が届かず困ります。
そもそも証明書なしでも展開できるような仕組みにして欲しかったです。(例えばクライアントが自身とClickOnce発行サーバのIP確認して同じサブネットなら証明書なしでも起動できるとか。。。)
[PR]
by jehoshaphat | 2014-01-26 23:46 | .Net開発 | Trackback | Comments(0)
(C#)ハンドルされない例外を捕まえる方法
以前に(VB.Net)ハンドルされない例外を捕まえる方法という記事を書きましたが、この内容のC#版のコードをメモ代わりに載せておきます。

まず、メインフォームのコンストラクタに以下のイベントハンドラを登録します。
//キャッチされない例外をとらえるイベント
Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
System.Threading.Thread.GetDomain().UnhandledException += new UnhandledExceptionEventHandler(Application_UnhandledException);


イベントハンドラは以下のようになります。

/// <summary>
/// 未処理例外をキャッチするイベントハンドラ。メインスレッド用。(WindowsForm専用)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e){
string sourceName = Properties.Settings.Default.AppName; //ソフト名が入っていることとする
//イベントログ出力
OutPutLogErr(e.Exception);
//メッセージボックス表示
MessageBox.Show("エラーが発生しました。処理を中断します。\nエラー情報:" + e.Exception.Message, sourceName, MessageBoxButtons.OK, MessageBoxIcon.Error);
//予期せぬ例外時は強制終了
Environment.Exit(-1);
}
 
/// <summary>
/// 未処理例外をキャッチするイベントハンドラ。別スレッドorコンソールアプリ用
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public static void Application_UnhandledException(object sender, UnhandledExceptionEventArgs e){
string sourceName = Properties.Settings.Default.AppName;
Exception ex = (Exception)e.ExceptionObject;
if (ex != null) {
//イベントログ出力
OutPutLogErr(ex);
//メッセージボックス表示
MessageBox.Show("エラーが発生しました。処理を中断します。\nエラー情報:" + ex.Message, sourceName, MessageBoxButtons.OK, MessageBoxIcon.Error);
//予期せぬ例外時は強制終了
Environment.Exit(-1);
}
}
 
 
/// <summary>
/// エラーをイベントログに出力。
/// </summary>
/// <param name="e"></param>
public static void OutPutLogErr(Exception e){
try{
string sourceName = Properties.Settings.Default.AppName;
if (!System.Diagnostics.EventLog.SourceExists(sourceName))
{
//ログ名を空白にすると、"Application"となる
System.Diagnostics.EventLog.CreateEventSource(sourceName, "");
}
byte[] myData = { };
string msg = "例外\n:" + e.Message + "\n例外スタックトレース:\n" + e.StackTrace + "\n";
if (e.InnerException != null)
{
msg = msg + "InnerException:\n" + e.InnerException.Message + "\nInnerExceptionスタックトレース:\n" + e.InnerException.StackTrace;
}
//イベントログにエントリを書き込む
//ここではエントリの種類をエラー、イベントIDを1、分類を1000とする
System.Diagnostics.EventLog.WriteEntry(sourceName, msg, System.Diagnostics.EventLogEntryType.Error, 1, 1000, myData);
} catch (Exception){
return;
}
}

[PR]
by jehoshaphat | 2014-01-14 00:07 | .Net開発 | Trackback | Comments(0)
(.Net)小数点第一位まで入力可という入力チェック
DataGridViewで、ある列は小数点第一位までの入力を許可させ、それ以外の値は認めないという要件を満たす方法です。

ちょっと悩んだんですが、まずfloatにパースできるかどうか判断します。
floatにパースできるようなら、10を掛け、それがintにパースできるかどうか判断します。
パースできないなら、小数点第二位以上の値が入っていることになるので、セルを移動させません。
というのが、以下のコードになります。(C#)


// 不正な場合、入力フォーカスを移動させない(小数点第一位まで可)
float f;
int i;
e.Cancel = (!float.TryParse(val, out f) || !int.TryParse((f * 10).ToString(), out i));


もっといい方法があるのかもしれませんが、これくらいしかスマートなのは思いつきませんでした。
[PR]
by jehoshaphat | 2014-01-13 00:56 | .Net開発 | Trackback | Comments(0)
(.Net)DataGridViewで右クリックしたときに行選択したい
DataGridViewでコンテキストメニューを割り当てた時、右クリックをしても、クリックしたセルに対してイベント走るのではなく、その時に選択されているセルに対してイベントが走ってしまいます。

Excelみたいに、右クリックしたら、マウスポインタの位置の行が選択されて、その後コンテキストメニューが出るようにしたい場合、以下のようにMouseDownイベントで行選択してしまえばいいようです。
参考先そのままですがコードを載せておきます。(C#)

/// <summary>
/// セルでマウスダウンイベントあった時
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void dgvParent_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
dgvParent.ClearSelection();
dgvParent.Rows[e.RowIndex].Selected = true;
}
 
}



参考:
DataGridView で右クリックして行を選択
[PR]
by Jehoshaphat | 2013-03-31 22:59 | .Net開発 | Trackback | Comments(0)
(.Net)文字列で桁を揃えるため0埋めしたい

下記のように文字列型で数値をいれているケースが有るとします。
string no = "1234";


これを7桁にし、先頭に0で埋めたいというケースの場合、String.PadLeft メソッドを使えばいいようです。
string no = "1234";
no = no.PadLeft(7,'0');


こうすると、0001234 という文字列になります。


ちなみに、数値型を0埋め文字列にしたい場合は、String.Formatが使えますね。
int num = 123;
string str = String.Format("{0:0000}", num);



参考:
指定の文字数になるまで先頭を文字で埋める
@IT:数値を右詰めや0埋めで文字列化するには?[C#、VB]
[PR]
by Jehoshaphat | 2012-05-16 00:32 | .Net開発 | Trackback | Comments(0)
(.Net)DataTable.Selectの条件は引用符でくくらないとおかしなことになる
DataTable.Selectメソッドを使って、データの抽出を行ってました。
下記のように検索対象の列は文字列型ですが、中身は数値が入っています。

string strNo = "10";
 
DataTable tbl = new DataTable();
tbl.Columns.Add("no");
tbl.Columns.Add("name");
 
tbl.Rows.Add(new object[] { "1", "hoge" });
tbl.Rows.Add(new object[] { "10", "hoge" });
tbl.Rows.Add(new object[] { "2", "hoge" });
tbl.Rows.Add(new object[] { "20", "hoge" });
 
DataRow[] rows = tbl.Select("no=" + strNo);


検索値を 10 とすると以下のような例外が select メッソド実行時に発生します。

System.ArgumentException がキャッチされました
Message="Range オブジェクトの Min (1) は、 max (-1) 以下でなければなりません。"
Source="System.Data"
StackTrace:
場所 System.Data.Select.GetBinaryFilteredRecords()
場所 System.Data.Select.SelectRows()
場所 System.Data.DataTable.Select(String filterExpression)
場所 Hoge.hoge() 場所 D:\mydoc\devlop\hoge.cs:行 12
InnerException:


ちなみに、検索値を他の値に設定するとこのエラーが出ません。

で、原因を調べた結果、MSDNフォーラム: VB2005 .NET2.0 DataTable.Select()メソッドに関してに答えが。。

ほぼコピペですが、詳しい話を。。
DataTable.Selectメソッドの内部ではバイナリサーチ(二分探索)を使っているようです。
バイナリサーチは、一旦リストをソートしてから出ないと実行できません。
検索列は文字列型なので、ソートすると下記のようになります。(文字列上の昇順になるわけです)

1,10,2,20

バイナリサーチはソートしたリストから中央の値を検索値と比較して、検索したい値が中央の値の右にあるか、左にあるかを判断するので、この場合、2番目以降(値10)に値があると判断されます。
そして、10の値がどこまで続いているかを調べるためにバイナリサーチではもう一回検索されるわけですが、その時検索値の 10 とリスト内の 2 が比較された結果、10 > 2となり、DataTable内の "2" と "20" の間に 10 があるはずだとなって、エラーになるようですね。

対策として、下記のようにSelectメソッドの検索値に引用符(シングルクォーテーション)を付けて明示的に文字列だとすればこのエラーは回避できます。

DataRow[] rows = tbl.Select("no='" + strNo + "'");


文字列検索するときは引用符の付け忘れに注意しましょう。
引用符つけなくても一応動いてしまうので厄介です。

参考:
DataTable.Select の条件式での型推論による弊害
[PR]
by Jehoshaphat | 2012-05-15 23:39 | .Net開発 | Trackback | Comments(1)
(.Net)DataTableへのアクセスで列名で指定すると遅くなる
DataTableの特定の列にアクセするときの列の指定で、列名でアクセスすると、DataColumnやインデックスでアクセスするときに比べ2倍ほど遅くなるようです。
なので、列にアクセするときはなるべくインデックスかDataColumnを使いましょう。

同じように、ループ時に一旦新たに DataRow を作成し、そこに代入、そのDataRowから各列にアクセスとするか、foreach でのアクセスが望ましいようですね。
Rowsへのアクセスも全行舐めるような処理の時に DataTable.Rows[rowIndex][colIndex] とすると上の方法に比べ、4倍程度遅くなるようです。


.SelectメソッドにいたってはLinqやループで検索するときに比べ数十倍遅いようです。データ量が多い時は Select メソッドは使い物にならないかもしれません。

また、DataTableの書き込みに関しても BeginEdit,EndEditの無い場合、有る場合と比較して十数倍ほど遅くなります。BeginEdit,EndEditは使ったほうがよさそうです。

DataTableは便利だけど遅いので、結局クラス化してそれをListコレクションにしたほうがいいのかもしれません。

参考:
[.NET][C#]当然っちゃ当然だけどDataTableとか使いようによっては遅い
[.NET][C#]当然っちゃ当然だけどDataTableとか使いようによっては遅い その2
意外と遅い DataTable 、なので List を使うと 5 倍早くなる | Moonmile Solutions Blog
[PR]
by Jehoshaphat | 2012-05-12 12:30 | .Net開発 | Trackback | Comments(0)