かきスタンプ

福岡でフリーランスの物流系のエンジニアやってます。

C#(MVVM):Vとバインドしたプロパティに対して VM側で値を変更する処理を記述する場合、アクセサでなくプライベート変数に変更をかけた方がいいのではないかという話。

Vとバインドしたプロパティに対して VM側で値を変更する処理を記述する場合、アクセサでなくプライベート変数に変更をかけた方がいいのではないかという話。    
 
以下、サンプルソースです。
今回の話題に必要なパートのみを抜粋した状態です。

<TextBox 
    Text="{Binding Path=MyVmString01, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
    />

<Button
    Content="AddText" 
    Command="{Binding Path=AddTextCommand}" 
    />
/// <summary>
/// プロパティ
/// </summary>
private string _myVmString01 = "初期値";
public string MyVmString01
{
    get
    {
        return this._myVmString01;
    }
    set
    {
        if (this._myVmString01 == value)
        {
            return;
        }
        this._myVmString01 = value;
        base.OnPropertyChanged(nameof(this.MyVmString01));
    }
}

/// <summary>
/// リレーコマンド
/// </summary>
private RelayCommand _addTextCommand;
public RelayCommand AddTextCommand
{
    get
    {
        return this._addTextCommand = this._addTextCommand ?? new RelayCommand(this.AddSpecialCharacter);
    }
}

/// <summary>
/// メソッド
/// </summary>
private void AddSpecialCharacter()
{
    _myVmString01 += "x";
    OnPropertyChanged(nameof(this.MyVmString01));
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string info)
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(info));
    }
}

 

概要

  • MyVmString01 は、V側の TextBox とバインドしている。
  • MyVmString01 の変更は、アクセサを通して V側に通知される。(OnPropertyChangedメソッド。)
  • AddTextCommand は、V側の Button とバインドしている。
  • AddTextCommand から AddSpecialCharacter() がコールされ、_myVmString01 の内容が変更される。

 
 
話がしたいのは、AddSpecialCharacter() メソッド。
myVmString01 += "x" 』と、プライベート変数の myVmString01 に変更を加えているけど、「別に『MyVmString01 += "x";』と書いても結果は一緒だから、どっちでもいいんじゃね?」と思ってた。
 
が、setterを使うと 変更をV側に伝えるために OnPropertyChangedが実行される事になり、V側に渡す内容を取得するために getも実行される。 (getアクセサにブレークポイントを置くと分かりやすい。)
 
つまり、メソッドに 1000回書き換えるループ処理があれば、1000回 V側に通知され、1000回 getが実行される。

非常にメモリに優しくないので、プライベート変数を編集した後、OnPropertyChanged をコールすると、getが1回で済む。
もしくは編集用の一時変数を用意して、最後にアクセサをコールするという方法でもいいかも。

C#(MVVM):ソースコードにてバインディング設定する方法

xamlでなく、ソースからバインド設定をするには、SetBinding メソッドを使用します。

(例)

Button myButton01 = new Button();
// Content に「MyString01」というプロパティとのバインディング設定を追加
myButton01.SetBinding(Button.ContentProperty, nameof(MyString01));
// Command に「SaveCommand」というリレーコマンドとのバインディング設定を追加
myButton01.SetBinding(Button.CommandProperty, nameof(SaveCommand));

//あとは適当に配置。

「MyString01」という stringを返すプロパティと、
「SaveCommand」というリレーコマンドが定義済みとしています。
 
 
上記のソースは、以下のような Xamlを記述した場合と同じ動きをします。

<Button
    Content="{Binding Path=MyString01}"
    Command="{Binding Path=SaveCommand}" 
    />

 
 
SetBinding の第二引数に、バインディングの対象にしたい”文字列”を設定します。
なので、上記のソースは、以下のように書くこともできます。

myButton01.SetBinding(Button.ContentProperty, "MyString01");
myButton01.SetBinding(Button.CommandProperty, "SaveCommand");

 
ただ、この書き方だと、リファクタリングに弱くなり、コードジャンプ機能も使えないので、nameof を使用した方がメンテナンス時の労力を軽減できます。

WPF:ListViewの列を、幅いっぱいに広げる(GridViewにアイテムをセットする場合)

GridView でアイテムをセットした ListView にて、セットした内容を横幅いっぱいに広げるには、カラムの Width を ListView の Width とバインディングさせる方法があります。

(例)
<ListView x:Name="myListView01" Width="300">
    <ListView.View>
        <GridView>
            <GridViewColumn
                Header="DispContent" 
                DisplayMemberBinding="{Binding DispContent}" 
                Width="{Binding ActualWidth, ElementName=myListView01}"
                />
        </GridView>
    </ListView.View>
</ListView>

カラムの Width のバインディング内容に、親となっている ListView の Nameを指定します。
 
上記では ActualWidth を指定していますが、幅が固定なら Width でも OKです。

WPF:XAML側で設定した値を、XAML側にてバインディング

V(XAML)と VMとのバインディングは頻繁に使いますが、XAML側で定義した値とのバインディングも可能です。
 
以下のように、「ElementName」に、XAML側にて定義したNameを指定します。

(例)
<TextBox
    Name="myTextBox01"
    Text="input" 
    />

<TextBlock
    Name="myTextBlock01"
    Text="{Binding Text, ElementName=myTextBox01}"
    />

この例では、TextBlock の Textに、TextBoxの内容をバインディングしています。

PL/SQL:update文の where条件に、 配列を使用する

以前、配列を where句における in の条件式として使用するというエントリを書きましたが、update文にて、この書き方をすると、ORA-00902: データ型が無効です。というエラーが発生します。
 
ですので、update文の where 条件にて、配列を引数に取るには、forall を使ってバルク処理をする事になります。

<記述例>
  type TYPE_NUM_LIST   is table of number  index by binary_integer;
  PROCEDURE MY_PROCEDURE_02_1 (
     IN_NUM_LIST_PARAM_1        IN  TYPE_NUM_LIST
    ,OUT_RETURN_CODE            OUT NUMBER
  ) IS
  
  -- 【※重要※】 .NETの配列を引数に渡したとき、インデックスは1から開始する
  headerIndex number := 1;
  BEGIN

    --登録データがなかった場合、-1を返して終了する
    IF (IN_NUM_LIST_PARAM_1.COUNT <= 0) THEN
      OUT_RETURN_CODE := -1;
      RETURN;
    END IF;

    --トランザクション開始
    SET TRANSACTION NAME 'TRANSACTION_MY_PROCEDURE_02_1'; 

    --更新(まとめてupdate)
    forall i in headerIndex..IN_NUM_LIST_PARAM_1.count
      update  TABLE1
         set  COLUMN2 = 9
             ,COLUMN3 = 'Param1'
       where  1=1
         and  ID = IN_NUM_LIST_PARAM_1(i)
       ;

    COMMIT;

  EXCEPTION
    WHEN OTHERS THEN
      OUT_RETURN_CODE := -1;
      ROLLBACK;

  END MY_PROCEDURE_02_1;

上記では、配列のインデックスを 1 から開始しています。
ODP.NET Managed Driver を使用し、.NET から Oracle に引数を渡して実行していますが、その時、配列のインデックスが、1 から開始するという、謎の挙動に頭を悩ませたからです。
 
インデックスを 0から開始させる方法を見つけきれなかったので、苦肉の策として上記のようなコードを書きました。

Oracle12c:PL/SQLのデバッグ実行に必要な設定【忙しい人向け】

Oracle 12cにて、PL/SQLデバッグするために必要な設定

  • DEBUG CONNECT SESSION 権限の許可
  • DBMS_DEBUG_JDWP に対する EXECUTE 権限
  • デバッグするストアド・プロシージャーに対する EXECUTE 権限
  • JDWP ACL 実行権限
  • ストアドプロシージャが "Compiled for Debug"であること

多すぎだろ!
 
 
という訳で、以下 PL/SQLデバッグ実行するために必要な設定を反映させるコマンド。

<注意点>
  • ローカルインストールされた DBを対象にしています。リモートに対して実行する場合、「localhost」「127.0.0.1」を、読み替えて下さい。
  • スキーマ名「KAKIP2」に権限を付与しています。適宜読み替えて下さい。
  • PUBLIC に権限を付与するのが嫌な方は、スキーマ名を指定してください。

 

SYSDBA 権限にて、sql plus から実行。
grant execute on SYS.UTL_ENCODE to PUBLIC;
grant execute on SYS.UTL_RAW to PUBLIC;
grant execute on SYS.UTL_I18N to PUBLIC
grant execute on SYS.UTL_SMTP to PUBLIC;
grant execute on SYS.DBMS_LOB to PUBLIC;
grant execute on SYS.DBMS_RANDOM to PUBLIC;
grant execute on SYS.DBMS_DEBUG_JDWP to PUBLIC;
grant execute on DBMS_DEBUG_JDWP to PUBLIC;

grant execute on SYS.UTL_HTTP to PUBLIC;
grant execute on SYS.UTL_TCP to PUBLIC;

grant debug connect session to PUBLIC;
grant debug any procedure to PUBLIC;
BEGIN
   DBMS_NETWORK_ACL_ADMIN.CREATE_ACL(
     acl         => 'localmail.xml',
     description => 'local mail acl',
     principal   => 'KAKIP2',
     is_grant    => true,
     privilege   => 'connect'
     );
   DBMS_NETWORK_ACL_ADMIN.ADD_PRIVILEGE(
     acl       => 'localmail.xml',
     principal => 'KAKIP2',
     is_grant  => true,
     privilege => 'resolve'
     );

  DBMS_NETWORK_ACL_ADMIN.ASSIGN_ACL(
     acl  => 'localmail.xml',
     host => 'localhost'
     );
  DBMS_NETWORK_ACL_ADMIN.ASSIGN_ACL(
     acl  => 'localmail.xml',
     host => '127.0.0.1'
     );
END;
/
COMMIT;
begin
       dbms_network_acl_admin.append_host_ace
       (host=>'127.0.0.1',
        ace=> sys.xs$ace_type(privilege_list=>sys.XS$NAME_LIST('JDWP') ,
                       principal_name=>'KAKIP2',
                       principal_type=>sys.XS_ACL.PTYPE_DB) );
    end;
/
後は、対象のファンクションを「デバッグ用にコンパイル

f:id:kakisoft:20180514235310p:plain

PL/SQL:配列を WHERE句における in の条件式として使用する

こんな感じ。

  type TYPE_NUM_LIST   is table of number  index by binary_integer;
  --////////////////////////////////
  --   配列データをINに渡して参照
  --////////////////////////////////
  PROCEDURE MY_PROCEDURE_02_4 (
     IN_NUM_LIST      IN  TYPE_NUM_LIST --配列型の引数
    ,OUT_LIST         OUT SYS_REFCURSOR     
    ,OUT_RETURN_CODE  OUT NUMBER
  ) IS

  BEGIN

    open OUT_LIST for 
      select
          *
      from
          TABLE1
      where  1=1
        and  ID in (select * from table(IN_NUM_LIST) )
    ;

  EXCEPTION
    WHEN OTHERS THEN
      OUT_RETURN_CODE := -1;
      ROLLBACK;

  END MY_PROCEDURE_02_4;

table関数を使って、配列をオブジェクト化しています。
 
 

【注意点】update文にて使用すると、エラーが発生します。

詳細は、こちらをご参照下さい。