セル単位での色指定 - umemura - 05-28-2013
RecordGrid で、セル単位に色をつけたい場合、どのようにすればよいでしょうか。
入力エラーがあるセルだけ背景を赤くすることが目的です。
カスタムセルを作成する必要があると思っていますが、
セルクラスのインスタンスは、カラムの中で使いまわされる(という認識の)ため、
インスタンスの背景色を直接指定すると、エラーでないセルに再利用されたときにも
背景色がそのままになってしまうと思います。
どのようにエラー状態を保持させるのがよいのでしょうか。
RE: セル単位での色指定 - dyoshida - 05-31-2013
カスタムセルを使って値が不正なセルの背景色に色をつけるなら、セルの更新が必要な際に呼ばれる
カスタムセルのrefresh-dataメソッドでセルの値のエラーチェックをして背景色を変えるのが簡単そう
ですがどうでしょうか
Code: || レコードセット
{def rs =
{RecordSet
{RecordFields
{RecordField "COL1", domain = String},
{RecordField "COL2", domain = String},
{RecordField "COL3", domain = String}
},
{RecordData COL1 = "NG", COL2 = "OK", COL3 = "OK"},
{RecordData COL1 = "OK", COL2 = "NG", COL3 = "OK"},
{RecordData COL1 = "OK", COL2 = "OK", COL3 = "NG"}
}
}
|| 入力値の有効性チェック(OKという文字列でなければ無効)
{define-proc public {is-invalid-val val:String}:bool
{return
{if val.empty? != true and
{val.to-upper-clone} != "OK"
then
true
else
false
}
}
}
|| カスタムセル
{define-class public MyCustomCell
{inherits StandardStringCell}
{constructor public {default}
{construct-super}
}
||スクロール等でセルに表示すべきデータが切り替わる場合に呼ばれる
{method public {refresh-data}:void
{super.refresh-data}
def (data:String, valid?:bool) = {self.get-formatted-data}
{if not valid? or || 値が取得できないまたは
self.selected? || 行が選択されている場合は何もしない
then
{return}
}
|| セルの値をチェック
|| 列毎にチェックメソッドを変更できるが、ここでは手抜きして
|| 全部同じチェックメソッドを呼んでいる
def invalid? =
{switch self.field.name
case "COL1" do
{is-invalid-val data}
case "COL2" do
{is-invalid-val data}
case "COL3" do
{is-invalid-val data}
else
true
}
|| チェック結果により背景色を変更
{if invalid? then
|| NG の場合は赤大文字
set self.background = "red"
set self.color = "pink"
else
|| OK の場合は通常の文字
{if not self.selected? then
{unset self.background}
{unset self.color}
}
}
}
}
{value
|| レコードグリッドを表示
{VBox
margin=2mm,
{bold OK以外の文字列を入力するとエラー},
{Fill height=2mm},
{RecordGrid
record-source = rs,
height = 3cm,
width = 10cm,
|| record-source 内のフィールドに対するカラムの自動生成を無効に設定
automatic-columns? = false,
|| 表示するカラムを明示的に生成
{RecordGridColumn "COL1", cell-spec = MyCustomCell},
{RecordGridColumn "COL2", cell-spec = MyCustomCell},
{RecordGridColumn "COL3", cell-spec = MyCustomCell}
}
}
}
どのような状況が思いつかないのですが、「エラー状態を保持~」と書かれていることから、
たとえばエラーチェックに非常に時間がかかるため、エラーチェックは入力時に一度のみ
しか行いたくないという場合のため、レコードセットに隠しフィールドを用意して、そこへ
チェック結果を保存する案も考えてみました。
チェック結果を格納するフィールドを1つにするために各フィールドのチェック結果合否を
1ビットで表現しているのでちょっと面倒になってしまいましたが・・・
Code: || 各フィールドの入力値チェック結果を格納する隠しフィールド名
{def flags-field-name = "flags"}
|| レコードセット
{def rs =
{RecordSet
{RecordFields
{RecordField "COL1", domain = String},
{RecordField "COL2", domain = String},
{RecordField "COL3", domain = String},
{RecordField flags-field-name, domain = int}
},
{RecordData COL1 = "NG", COL2 = "OK", COL3 = "OK", flags = 1},
{RecordData COL1 = "OK", COL2 = "NG", COL3 = "OK", flags = 2},
{RecordData COL1 = "OK", COL2 = "OK", COL3 = "NG", flags = 4}
}
}
|| チェック結果フラグから指定フィールドのチェック結果フラグを取り出すプロシージャ
{define-proc public {set-invalid-flag
invalid?:bool,
flags:int,
field-index:int
}:int
|| フィールド位置のチェック結果のビットを設定するためのビットフィールドを作成
def bit-fields = {bit-sll 0x1, field-index}
|| 既存のフラグに指定位置のビットをセットした新しいフラグを作成
def new-flags =
{if invalid? then
{bit-or flags, bit-fields}
else
{bit-and flags, {bit-not bit-fields}}
}
{return new-flags}
}
|| チェック結果フラグから指定フィールドのチェック結果フラグを取り出すプロシージャ
{define-proc public {get-invalid-flag
flags:int,
field-index:int
}:bool
|| フィールド位置のチェック結果のビットを取り出すためのマスクを作成
let bit-mask:int = {bit-sll 0x1, field-index}
||チェック結果フラグのビットフィールドから目的のフラグを取り出す
def flg:int = {bit-and flags, bit-mask}
def cell-valid? =
{if flg != 0 then
false
else
true
}
{return cell-valid?}
}
|| セルに格納されているカラムのチェック結果を取り出すプロシージャ
{define-proc public {validate-cell cell:StandardStringCell}:bool
|| セルに格納されている値のレコードセットにおけるフィールド位置を取得
def rf:#RecordField = cell.field
def field-name:String = rf.name
def rec:#Record = cell.record
def rs:#RecordSet = rec.record-set
def rfs:#RecordFields = rs.fields
def field-index:int = {rfs.get-index field-name}
|| チェック結果フラグを隠しフィールドから取得
def v-flags = {rec.get flags-field-name} asa int
||チェック結果フラグのビットフィールドから目的のフラグを取り出す
def cell-valid? = {get-invalid-flag
v-flags,
field-index
}
{return cell-valid?}
}
|| 入力値の有効性チェック(OKという文字列でなければ無効)
{define-proc public {is-invalid-val val:String}:bool
{return
{if val.empty? != true and
{val.to-upper-clone} != "OK"
then
true
else
false
}
}
}
|| カスタムセル
{define-class public MyCustomCell
{inherits StandardStringCell}
{constructor public {default}
{construct-super}
}
||スクロール等でセルに表示すべきデータが切り替わる場合に呼ばれる
{method public {refresh-data}:void
{super.refresh-data}
def (data:String, valid?:bool) = {self.get-formatted-data}
{if valid? and
not self.selected? ||行が選択されている場合は何もしない
then
|| セルの値のチェック結果を取得
def valid-cell? = {validate-cell self}
{if valid-cell? != true then
|| NG の場合は赤大文字
set self.background = "red"
set self.color = "pink"
else
|| OK の場合は通常の文字
{if not self.selected? then
{unset self.background}
{unset self.color}
}
}
}
}
}
|| セル更新時の入力値チェック用イベントハンドラ
{def rec-mod-event-handler =
{on rm:RecordModified at rs:RecordSet do
|| 更新されたフィールド名を取得
def field-name = rm.field.name
|| チェックフラグのフィールドが更新された場合はなにもしないで抜ける
{if field-name == flags-field-name then
{return}
}
|| 更新された値を取得して無効か否かをチェックする
def current-record = rm.record
def val:String = {current-record.get field-name} asa String
def invalid? = {is-invalid-val val}
|| 更新フィールドのインデックス位置を指定して、チェックフラグのフィールドへ
|| チェック結果のフラグを設定する
def rfs:#RecordFields = rs.fields
def field-index:int = {rfs.get-index field-name}
def flags = {current-record.get flags-field-name} asa int
def new-flags = {set-invalid-flag invalid?, flags, field-index}
{current-record.set flags-field-name, new-flags}
}
}
{value
|| レコードセットにセル入力値チェックのイベントハンドラをセット
{rs.add-event-handler rec-mod-event-handler}
|| レコードグリッドを表示
{VBox
margin=2mm,
{bold OK以外の文字列を入力するとエラー},
{Fill height=2mm},
{RecordGrid
record-source = rs,
height = 3cm,
width = 10cm,
|| record-source 内のフィールドに対するカラムの自動生成を無効に設定
automatic-columns? = false,
|| 表示するカラムを明示的に生成
{RecordGridColumn "COL1", cell-spec = MyCustomCell},
{RecordGridColumn "COL2", cell-spec = MyCustomCell},
{RecordGridColumn "COL3", cell-spec = MyCustomCell}
}
}
}
RE: セル単位での色指定 - umemura - 05-31-2013
現在私が想定しているエラー状態を保持、というのは、2つ目にご提示いただいた方法がより合うと思っています。
というのも、値に対するチェックロジックが、クライアントだけで完結すればよいのですが、
サーバーのチェックロジックの結果を保持したい、というような場合がありえるからです。
たとえば、マスタコードの存在チェックなどがそれに当たります。
エラー時のツールチップメッセージや、入力可否なども同様に隠しフィールドで持つのがよさそうだと思っています。
ただ、エラー有無や入力可否、ツールチップメッセージなど、
セルの状態をすべて個別に隠しフィールドで持とうとすると、
フィールド数がそれだけ増えていってしまうので、
セルの状態を保持するクラスを用意して、ひとつのフィールドに対して、
ひとつのセル状態保持用隠しフィールドで済むような実装を考えて見ます。
RE: セル単位での色指定 - dyoshida - 05-31-2013
状況のご説明ありがとうございます。
たしかにチェックに外部DBとの接続が必要だとセルの表示準備毎に実行するわけにはいかないですね・・・
RE: セル単位での色指定 - michaeljee - 06-13-2013
この短い形式についての素晴らしい驚き。それはあなたが話して/について書いているあるものを教えてできますか?
すべての情報は持っています。この短い形式について前に投稿さ(と私はちょうどそれを逃した)されていますか?
RE: セル単位での色指定 - umemura - 06-17-2013
とりあえず、フィールドで持ってみました
Code: ||セルの振舞いを定義する Mixinクラス
{define-class public abstract CellReacterMixin
field public cell-reacter-proc:#{proc-type {RecordGridCell}:void}
{constructor public {default
cell-reacter-proc:#{proc-type {RecordGridCell}:void} = null,
... }
set self.cell-reacter-proc = cell-reacter-proc
}
||セルの振舞いを処理する 振舞いは独自に指定できるようにプロシージャで定義
{method public {set-custom-react cell:RecordGridCell}:void
{if-non-null react-proc = self.cell-reacter-proc then
{react-proc cell}
}
}
{getter public {as-RecordGridCell}:#RecordGridCell
{type-switch self
case rgc:RecordGridCell do
{return rgc}
else
{return null}
}
}
{method public {get-cell-react-option option-name:String}:any
{if-non-null cell = self.as-RecordGridCell then
{if-non-null record = cell.record, rf = cell.field then
{return {cell.record.get rf.name & option-name}}
}
}
{return null}
}
}
||読み取り専用背景色
{def public READONLY_BACKGROUND_COLOR :String = "#D0D0D0"}
||エラー用背景色
{def public ERROR_BACKGROUND_COLOR:String = "#FFB0B0"}
||必須入力用背景色
{def public REQUIRED_BACKGROUND_COLOR:String = "#FFFFCC"}
||カスタムレコードフィールドのコード名
{def public ERROR-FIELD-NAME:String = "_error?"}
{def public READ-ONLY-FIELD-NAME:String = "_read-only?"}
{def public REQUIRED-FIELD-NAME:String = "_required?"}
||エラー、表示のみ、必須 のプロパティを指定するための実装
{define-class public DefaultCellReactMixin {inherits CellReacterMixin}
field constant error?:String = ERROR-FIELD-NAME
field constant read-only?:String = READ-ONLY-FIELD-NAME
field constant required?:String = REQUIRED-FIELD-NAME
{constructor public {default ...}
{construct-super {splice...}}
}
||セルの振舞いを定義するためのフィールドを追加するプロシージャ
{define-proc public {get-cusotmed-field fields:RecordFields}:RecordFields
def ary-rf:{Array-of RecordField} = {{Array-of RecordField}}
{for rf in fields do
{ary-rf.append rf}
let read-only-def-val:bool = false
let required-def-val:bool = false
{ary-rf.append {RecordField rf.name & ERROR-FIELD-NAME,
domain = bool, default-value = false}}
{ary-rf.append {RecordField rf.name & READ-ONLY-FIELD-NAME,
domain = bool, default-value = read-only-def-val}}
{ary-rf.append {RecordField rf.name & REQUIRED-FIELD-NAME,
domain = bool, default-value = required-def-val}}
}
{return {RecordFields {splice ary-rf}}}
}
{define-proc public {default-cell-reacter cell:RecordGridCell}:void
{type-switch cell
case react-mxin:DefaultCellReactMixin do
||選択されている場合は、背景色、文字色が設定済みなので、後続の処理を行わない
{if not cell.selected? then
{if-non-null record = cell.record then
|| 表示のみの反映が高位なので、最初に判断
{if {react-mxin.get-cell-react-option react-mxin.read-only?} then
set cell.editable? = false
set cell.background = READONLY_BACKGROUND_COLOR
elseif {react-mxin.get-cell-react-option react-mxin.error?} then
||エラー時は背景を赤色にする
set cell.background = ERROR_BACKGROUND_COLOR
elseif {react-mxin.get-cell-react-option react-mxin.required?} then
||必須時は背景を黄色にする
set cell.background = REQUIRED_BACKGROUND_COLOR
else
{unset cell.background}
}
}
}
}
}
}
||セルの振舞いを独自に指定したセル
{define-class public open CustomReactCell
{inherits StandardStringCell, DefaultCellReactMixin }
{constructor public {default ...}
{construct-super.StandardStringCell}
{construct-super.DefaultCellReactMixin
cell-reacter-proc = DefaultCellReactMixin.default-cell-reacter,
{splice ...}}
}
{method public open {refresh-data}:void
{super.refresh-data}
{self.set-custom-react self}
}
}
{let people:RecordSet =
{RecordSet
{DefaultCellReactMixin.get-cusotmed-field
{RecordFields {RecordField "First", domain = String} }
},
{RecordData First = "John"},
{RecordData First = "Jane"},
{RecordData First = "Jane"}
}
}
{def grid =
{RecordGrid
height = 1.5in,
automatic-columns? = false,
{RecordGridColumn cell-spec = CustomReactCell, "First" },
record-source = people
}
}
{def real-grid =
{RecordGrid
width = 6in,
height = 1in,
record-source = people
}
}
{VBox
grid,
real-grid
}
RE: セル単位での色指定 - umemura - 10-28-2013
レコードの修正のたびに refresh-data が呼び出されるので、
まとまった量のレコードを変更する処理が走ると、
描画のパフォーマンスが低下します。
通常、そういう場合は、レコードセットのバッチイベントを true にして実行するので、
バッチイベント状態を見てセルの描画をするかどうかを判断すると、かなりパフォーマンスが良くなりました。
別スレッドで投稿した、グリッドの行番号を表示するためのセルにも同様のことが言えます。
|