2015年11月5日木曜日

【VBA for Excel】分割された条件付き書式の統合

Excelおいて条件付き書式を設定し、設定されたセルをコピー&貼り付けを行った場合に、
条件付き書式が分割されてしまうという既知の問題に対して、
未然に防ぐ方法はいくつかあるものの、
分割されてしまった場合に同条件だった場合に統合する方法が見つからなかった。
これらを事後においても統合できるようにするため、
VBAにて統合処理のアドインを作成した。

 アドイン

アドインより追加すると「アドイン」というタブが追加され、「メニュー コマンド」というグループ内に
「条件付き書式の統合」というボタンが追加される。
これをクリックすることで条件書式の統合が行われる。

同条件同書式に関しては全て統合してしまうため、
あえてグループ毎に分けている場合なども統合されてしまうので注意。

※本アドインの使用により発生した損害については、一切関知致しません。
  使用は個人責任でお願い致します。



一番面倒かと思われた数式の一致判定に関しては、
セル値と思われる箇所を正規表現で取得し、
それらをした文字列が同様かどうかを比較。
その後絶対参照か絶対参照を判定し、絶対参照であれば一致するかどうか、
相対参照であれば適用範囲の先頭セルとの相対位置が一致するかどうかで、
同数式かどうかを判定で思いの外簡単に対応できた。

あとは全条件、全書式網羅を粛々と対応するだけの作業。

主にはFormatConditionクラスのオブジェクトに対応すればよいものの、
各条件によって、使用しているクラスが異なるかつ、インタフェース実装でもないため、
リフレクションによってアクセスしなければいけないのがExcel側の設計としてどうかと思われる。
リフレクションしたうえで実装されていないメソッド、プロパティがあった場合は、
実行時エラー不可避というのも改善して欲しい。
※何か回避策を知っている人がいたら教えてください。
  CallByName関数でアクセスしているのですが、
  IsImplimented関数(仮)などでサポートしているかどうかの事前チェックがしたい。

また、罫線に関しては、上下左右の4か所しか設定できないはずが、
Bordersオブジェクトが6つのBorderオブジェクトを持っているケースがあり、(CellValuesなど)
5つ目、6つ目を参照すると、
LineStyleプロパティをサポートしていないという問題が発生してしまう。
暫定として4つで判定しているが、(単一セルの書式設定なので問題ないはず?)
もし必要になる場合があるならば不具合となってしまう。

現状、テストした環境では問題なく動作しているため、
必要に応じて改良を加えていく。

2015年4月19日日曜日

【C#】DataTableをバインドしたDataGridViewのソートをアンドゥー・リドゥーする


DataTableをバインドしたDataGridViewのソートをしたかったものの、
検索に引っかからなかったのでメモ。
とりあえずは列のソートのみでサンプルを作成。

キモとしてはDataViewをIBindingListにキャストして、
SortProperty、SortDirectionプロパティを取得及び設定するところ。
デフォルトはSortPropertyはnull、SortDirectionはAscendingとする。

取得を行うためのソート前のイベントとして適当なものが無かったため、
あくまでもソートをするためにはヘッダーセルをクリックする前提で
CellMouseEnterイベントを使用した。
まずは当該イベントにて、
SortProperty、SortDirectionプロパティよりソート前の値を取得する。
あくまでもソートする候補として都度取得を行うのみとし、
ソート後にソート前の値として使用する。

その後、ソート後のイベントではSortProperty、SortDirectionプロパティより
ソート後の値を取得し、コマンドを作成する。
コマンドの作成は検索してすぐに見つかると思われるので、省略。

ソート前の列とソート後の列が一緒かどうかの検証を行うべきかと思われるが、
上記のとおり、ヘッダーセルをクリックする前提であれば、問題なく動作している模様。

PropertyDescriptor prevSortProperty_; // ソート前のソートプロパティ
ListSortDirection prevSortDirection_; // ソート前のソート方向

void dataGridView1_CellMouseEnter(object sender, DataGridViewCellEventArgs e)
{
    // 列ヘッダーセルにマウスが入った場合のみ、
    // 各プロパティの値を取得
    if(e.RowIndex == -1)
    {
        IBindingList bindingList =
            (IBindingList)this.dataGridView1.DataSource.DefaultView;
        prevSortProperty_ = bindingList.SortProperty;
        prevSortDirection_ = bindingList.SortDirection;
    }
}

void dataGridView1_Sorted(object sender, EventArgs e)
{
    // ソート後のソートプロパティとソート方向を取得
    IBindingList bindingList =
        (IBindingList)this.dataGridView1.DataSource.DefaultView;
    PropertyDescriptor currentSortProperty = bindingList.SortProperty;
    ListSortDirection currentSotrDirection = bindingList.SortDirection;

    // prevSortProperty_、prevSortDirection_、
    // currentSortProperty、currentSotrDirectionを使用して、
    // ソートのアンドゥー・リドゥーコマンドを作成
}

2015年3月24日火曜日

【TeraStation】HDD換装でのEMモードからの復旧

1TB×2のRAID1にて運用していたTeraStationの片方のHDDが壊れてしまったため、
4TB×2のHDD入れ替えを検討。

HDDを両方とも入れ替えたところ、EMモードとなってしまった。
Web管理画面にもアクセスできず、ファームアップデートを試みたものの、
パーティション情報が無いと言われてアップデートできずに試行錯誤。

アップデータに含まれるTSUpdater.iniというファイルの
VersionCheck, NoFormattingを0にしたところ、
アップデートをすることが出来た。

パーティション情報が無いと言われるのは当然としても、
アップデートが出来ないのは納得できない。

それよりも、上記書き換えについて、分りやすく書いていてほしかった。
(正規のやり方ではないかもしれないが。)

2015年3月12日木曜日

【C#】XmlSerializerではsetアクセサーが呼ばれない

Dictionary<string, string>を保持したクラスにて、
インスタンス丸ごとシリアライズ・デシリアライズを行うため、
以下のようなプロパティを作成して実行した。 

public List<KeyAndValue<string, string>> Parameters
{
    set { Dictionary = ListToDictionary(value); }
    get { return DictionaryToList(value); }
}

シリアライズは問題なく行われたものの、
デシリアライズ後のDictionaryは空のまま。

シリアライズ時はget、デシリアライズ時はsetを行うのではという想定で、
setアクセサにブレークを張るものの引っかからない。

試しにgetアクセサにてブレークを張ったところ、
予想外に引っかかった。

なぜ?と言うことで調べて見たところ、
以下の記事が見つかった。

 http://goo.gl/W94Md0

XmlSerializer does not call the setter for a collection. Instead is calls the getter, and then adds items to the collection returned.
setアクセサを呼ばずに、getアクセサで取得したコレクションに対してアイテムを追加すると。

原因判明。



2012年9月2日日曜日

【Redmine】ガントチャートpdf出力拡張 vol.1.5 進捗度の描画ずれ修正

目的
前回、ガントチャートのpdf出力表示にて、
日付毎に縦線を引いたが、
バーのゲージや、端、マイルストンの位置がずれていた。
これを修正する。


作業手順
引き続き、
apps\redmine\backup\lib\redmine\helpers\gantt.rbを解析。

各項目毎の進捗は、
line_for_xxx(xxxはワイルドカード)メソッドにて行われているのは前回で分かった。
line_for_xxxメソッドの内部を見てみる。

coordinatesメソッドで座標を求め、
pdf_taskメソッドで描画を行っているようだ。

coordinatesメソッド内の最後の方にある、
以下の処理にて、floorメソッドを使用している。
coords.keys.each do |key|
  coords[key] = (coords[key] * zoom).floor
end
coords[key]もしくは、zoomが小数で、
かつpdf出力時は小数を許容する場合、
誤差が生じることになる。


まずは、
coords[key]もしくはzoomが小数かどうか調査する。

xの値を調べる場合、
ActiveRecord::Base.logger.info x
と記述することで、ログファイル(apps\redmine\htdocs\log内)に出力される。

coords[key]及び、zoomの値を出力してみたところ、
zoomの値は小数であった。(正確にはRational。)


次に、
pdf出力時は小数を許容するのか調査する。

描画する際に、gantt.rbでは、
pdf_taskメソッド内でRDMCellメソッドを呼び出している。

RDMCellメソッドは、
pdfオブジェクトを使用しているが、
これはto_pdfメソッド内の以下の処理にて生成されている。
pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language)
ITCPDFクラスは、
apps\redmine\htdocs\lib\redmine\export\pdf.rbに記述されている。
RDMCellメソッドは、ITCPDFクラスのインスタンスメソッドであり、
Cellメソッドを呼び出しているだけである。
CellメソッドはITCPDFの親クラスである
TCPDFクラスのインスタンスメソッドである。

TCPDFクラスは、
apps\redmine\htdocs\lib\plugins\rfpdf\lib\tcpdf.rbに記述されている。
Cellメソッドのコメントを見ると、
第一、第二引数ともにfloatになっている。


これらからの調査結果から、
floorメソッドの呼び出しが原因である可能性が高い。
よって、pdf出力時にはfloorメソッドを呼ばないように変更をかける。


まずは、coordinatesメソッド内の最後にある、
floorメソッド呼び出し部分を以下のように変更する。
coords.keys.each do |key|
  coords[key] = coords[key] * zoom
  coords[key] = coords[key].floor unless format == :pdf
end
次に、coordinatesメソッドの引数にformatを追加する。
def coordinates(start_date, end_date, progress, zoom=nil, format=nil)
最後に、全てのcoordinatesメソッドの呼び出しに、
options[:format]を追加する。

以上で、作業完了ある。


変更後のソースコードは以下にアップされている。
『進捗度の描画ずれ修正 』のフォルダにある。
変更前のソースコードは『日付表示を見やすく 』のフォルダにある、
変更後のソースコードを参照してもらいたい。

ソースコード




結果
修正前:


修正後:


ずれはなくなっているようだ。

2012年8月19日日曜日

【Redmine】ガントチャートpdf出力拡張 vol.1 日付表示を見やすく

目的
ガントチャートpdf出力表示にて、
日付単位の表示が曜日になっているのを日付に変更、
日付毎に縦線を引き、
土日の文字色、背景色を変更する。
また、項目毎に横線を引く。


作業手順
app\controllers\gantts_controller.rbを見ると、
45行目に、format.pdf ...とあり、
ヘルパーであるganttのto_pdfメソッドを呼び出している。
これがpdfファイル作成処理のようだ。

apps\redmine\htdocs\lib\redmine\helper\gantt.rbを見ると、
to_pdfメソッドがあるので解析。
to_pdfメソッド内では、月単位、週単位、日付単位のヘッダを描画、
renderメソッドにて、
以降のチケットの進捗度部分を作成していることが分かった。

まずは、to_pdf内を変更し、
日付単位の曜日表示を日付表示に変更する。

587行目、
wday = self.date_from.cwday
にて、表示曜日の初期値設定を行っているが。
次の行に表示日付の初期値設定を追加する。
date = self.date_from
 
次に、(上記変更により)594行目、
pdf.RDMCell(width, height, day_name(wday).first, "LTR", 0, "C")
にて、曜日名を表示しているが、日付に変更するため、
pdf.RDMCell(width, height, date.day.to_s, "LTR", 0, "C")
に変更。


次に、(上記変更により)596、597行目、
wday = wday + 1
wday = 1 if wday > 7
にて、表示曜日の更新処理を行っているが、 
その次の行に、表示日付の更新処理を追加する。
date = date + 1
これで曜日から日付への変更が完了。


次に、土日の文字色の変更。
(上記変更により)591行目の下に、
以下の処理を追加。
case(wday)
when 6
  pdf.SetTextColor(0, 0, 255)
when 7
  pdf.SetTextColor(255, 0, 0)
else
  pdf.SetTextColor(0, 0, 0)
end

繰り返しを抜けた箇所に、
文字色を戻すため、以下を追加して完了。

pdf.SetTextColor(0, 0, 0)



次に、縦線を引く。
そのためには、
各プロジェクト、バージョン、イシュー毎の
描画描画処理を行っている箇所を解析する必要がある。
renderメソッドを見ていく。

renderメソッドでは、プロジェクトツリーに従い、
各プロジェクト毎にrender_projectメソッドを呼び出している。

render_projectメソッドでは、
まず、subject_for_projectメソッド、line_for_projectメソッドを呼び出し、
プロジェクトの項目名、 進捗度の描画を行っている。

次にバージョンに含まれないイシューの描画のため、
render_issuesメソッド内で各イシュー毎に
subject_for_issueメソッド、line_for_issueメソッドを呼び出し、
イシューの項目名、進捗度の描画を行っている。

次にバージョンのため、
render_versionメソッド内で各バージョン毎に
subject_for_versionメソッド、line_for_versionメソッドを呼び出し、
バージョンの項目名、ラインの描画を行ってい、
バージョンに含まれるイシュー毎に、
render_issuesメソッドを呼び出している。

line_for_xxxメソッド(xxxはワイルドカード)にて、
ヘッダー描画時の日付毎の縦線描画処理を入れることで
容易に対処できそうなので、
line_for_xxxメソッド内に処理を追加する。

しかし、subject_for_xxxメソッドでは、
各項目の外枠を描画しているようなので、
後に回すことで日付毎の線を色を変えて引く際に
端かどうか判定してする必要も無くなる。

ということで、
まずは、line_for_xxxメソッド内に当該処理を追加する。
対象となるメソッドはline_for_projects、line_for_version、line_for_issueである。
各メソッドの先頭に処理を追加する。
追加処理はpdf出力時しか使用しないので、
条件式で括っておく。 必要な値は全てoptions内に含まれるため、
引数はoptionsとし、以下の処理を追加する。(※line_for_projectの場合。)
if project.is_a?(Project) && options[:format] == :pdf
  render_day_separator(options)
end

project.is_a?(Project)は以降の処理に含まれているものであるため、
とりあえず追加している。
この処理はline_for_xxxメソッドによって変更する必要がある。

次に、render_day_separator内で、
日付表示だった場合に描画を行うため、
optionsに日付表示フラグを追加する必要がある。
そのため、to_pdfメソッドでoptionsにshow_daysを追加する。
617行目、
options = {
  :top => top,
  :zoom => zoom,
  :subject_width => subject_width,
  :g_width => g_width,
  :indent => 0,
  :indent_increment => 5,
  :top_increment => 5,
  :format => :pdf,
  :pdf => pdf
}
 に、:show_days => show_daysを追加。
options = {
  :top => top,
  :zoom => zoom,
  :subject_width => subject_width,
  :g_width => g_width,
  :show_days => show_days,
  :indent => 0,
  :indent_increment => 5,
  :top_increment => 5,
  :format => :pdf,
  :pdf => pdf
}

render_day_separatorメソッドに関しては、
少し長いのでソースコードを参照してほしい。
こちらに背景色の変更処理も含まれている。
コメントで土日の背景色設定と書かれている処理がそれにあたる。



次に、subject_for_xxxメソッドとline_for_xxxメソッドの処理順を入れ替える。
その際に、subject_for_xxxメソッド内にある、
pdf_new_page?メソッドをline_for_xxxメソッド内に移動する必要がある。
作業としては、上記追加箇所に追加するだけで良い。

if project.is_a?(Project) && options[:format] == :pdf
  pdf_new_page?(options)
  render_day_separator(options)
end
長かったので、ソースコードを見て、
各自確認をしてもらえればと思う。


変更前、変更後のソースコードは以下にアップされている。
『日付表示を見やすく 』のフォルダにある。

ソースコード



結果
大分見やすくなったはず。
ただ、バーのゲージや端、マイルストンの位置がずれているように見える。
次回、原因を調査する。









2012年8月12日日曜日

【Redmine】チケットの題名を正規表現でフィルタリング

目的
チケットの題名にて、
プレフィックスとして、□や、■など、意味を持たせた場合に、
□及び、■を含むチケットのフィルタリングを行いたい。
現状題名の操作にある、『含む』では、
正規表現での□.*というフィルタリングしか行えないため、
汎用性を持たせるため、
題名の操作に正規表現を追加し、
正規表現による題名のフィルタリングを行えるようにする。


作業手順
Ruby on Rails自体、詳細に把握しているわけではないので、
出力されたhtmlからボトムアップ的に調査する。

チケット一覧ページのhtmlには、
<legend onclick="toggleFieldset(this);">フィルタ</legend>
とある。 




まずは、issuesによるページに含まれるので、
app/models/index.html.erbを見る。
<legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
という行があり、config\locales\ja.ymlにて、
label_filter_pluralをみるとフィルタと関連付けられているため、

ここが当該箇所だと断定。
 
その下の行を見てみると、
div内でrenderで'queries/filters'を呼んでいる箇所がある。

 
app\views\queries\_filters.html.erbを見ると、
query.available_filtersに含まれるフィルタ分、GUIを追加している。
題名のフィルタ操作に正規表現を追加したいので、
題名のタイプを見るとテキストになっているため、
テキストの操作に正規表現を追加してやればよい。
そのため、@@operatorsに、
"r"   => :label_regexp
を追加、
@@operators_by_filter_typeの:textを
:text => [  "~", "!~", "!*", "*", "r" ]
に変更。

:label_regexpのため、config\locales\ja.ymlに、
label_regexp: 正規表現
を追加。

最終的にSQL文が発行されるので、
sql_for_field関数に以下を追加。
when "r"
  sql = "LOWER(#{db_table}.#{db_field}) REGEXP '#{connection.quote_string(value.first.to_s.downcase)}'"

変更前、変更後のソースコードは以下にアップされている。
『チケットの題名を正規表現でフィルタリング』のフォルダにある。

ソースコード


結果
無事追加されている模様。


動作に関しても、それっぽく動作している。






と思いきや、REGEXPがマルチバイト対応されていないために、
日本語が含まれている場合に不具合が発生する。
後ほど、マルチバイト対応も記す。