読者です 読者をやめる 読者になる 読者になる

nakamurakko’s diary

プログラマー

DocumentCompositeNodeにキャストできません

実行環境:Visual Studio 2017 Professional

困った事象

WPFで、Styleなどを定義した外部ResourceDictionaryをExpander.HeaderTemplate内に適用したくて、次のように書いた。

<Window x:Class="WpfDataTemplateStyleNG.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfDataTemplateStyleNG"
        mc:Ignorable="d"
        Title="WpfDataTemplateStyleNG"
        Height="200"
        Width="300">

    <StackPanel>
        <Expander>
            <Expander.HeaderTemplate>
                <DataTemplate>
                    <DataTemplate.Resources>
                        <!--DataTemplateで外部リソースを設定。-->
                        <ResourceDictionary Source="Styles.xaml" />
                    </DataTemplate.Resources>

                    <TextBlock Text="Expander.HeaderTemplate上のTextBlock" />
                </DataTemplate>
            </Expander.HeaderTemplate>

        </Expander>

        <TextBlock Text="StackPanel上のTextBlock" />
    </StackPanel>

</Window>

コードは問題なさそうだけど、こんなエラーが出た。(ただ、エラーにはなるけど、ビルドは正常に完了するし、実行も問題なくできる。)

'OnDemandResourceDictionary' のオブジェクトを型 'Microsoft.VisualStudio.DesignTools.Markup.DocumentModel.DocumentCompositeNode' にキャストできません。

調べてみると、Microsoftに報告されていて、対応しないことになっている。

XAML designer broken when adding resource dictionaries to data or control templates


回避策

エラーになるのは困るので、回避策としては、

  • StaticResourceを使う。
  • Template内で使用する前にカスタムコントロール化して、カスタムコントロールの方で外部リソースを読み込む。

という感じですかね。

ちなみに、上記Expanderの場合は次のようにHeaderTemplateではなく、Headerに直接書けば回避できた。

<Window x:Class="WpfDataTemplateStyle.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfDataTemplateStyle"
        mc:Ignorable="d"
        Title="WpfDataTemplateStyle"
        Height="200"
        Width="300"
        FontSize="15">

    <StackPanel>
        <Expander>
            <Expander.Header>
                <HeaderedContentControl>
                    <HeaderedContentControl.Resources>
                        <!--これはエラーにならない。-->
                        <ResourceDictionary Source="Styles.xaml" />
                    </HeaderedContentControl.Resources>

                    <TextBlock Text="Expander.HeaderTemplate上のTextBlock" />
                </HeaderedContentControl>
            </Expander.Header>

        </Expander>

        <TextBlock Text="StackPanel上のTextBlock" />
    </StackPanel>

</Window>

ControlTemplate、DataTemplateで外部リソースを書いてもエラーにならない場合があるけど、条件は分からない。

Visual Studio 2017のキーマップを変更して、CTRL + PageUp、CTRL + PageDownでタブ切り替えしたい。(自分用)

キーボードマップ スキームが「既定」の場合に、オプション - 環境 - キーボードで設定する。

  • CTRL + PageUp
    • 「編集.上端まで移動」を選択して、ショートカットを削除。
    • 「ウィンドウ.前のタブ」にCTRL + PageUpが割り当てられているか確認。
  • CTRL + PageDown
    • 「編集.下端に移動」を選択して、ショートカットを削除
    • 「ウィンドウ.次のタブ」にCTRL + PageDownが割り当てられているか確認。

Visual Studio 2012以降なら同じように設定できる気がする。

WPF StatusBarの項目を右寄せ

StatusBarの項目を右寄せしたいと思っていたら、StatusBarItemに「DockPanel.Dock="Right"」を指定すればいいらしい。
(ただ、DockPanelを使っていないのにDockPanel.Dockが使える理由が分からない。)

ソース

<Window x:Class="WpfStatusBarSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfStatusBarSample"
        mc:Ignorable="d"
        Title="WpfStatusBarSample"
        Height="200"
        Width="300">
    <Grid>

        <!--ステータスバー。-->
        <StatusBar VerticalAlignment="Bottom">

            <!--「DockPanel.Dock="Right"」で右に寄せている。-->
            <StatusBarItem DockPanel.Dock="Right">
                Right
            </StatusBarItem>

            <!--左寄せにしたい項目は後で定義する。-->
            <StatusBarItem>
                Left
            </StatusBarItem>

        </StatusBar>

    </Grid>
</Window>

実行結果
f:id:nakamurakko:20170316223543p:plain

C# Lambdaでループを書き換えてみる

「yyyyMMdd」形式の文字列を用意する。 

  1. // 「yyyyMMdd」形式で日付を表す文字列のリスト。
  2. List<string> stringDateTimes = new List<string>();
  3. stringDateTimes.Add("20161011");
  4. stringDateTimes.Add("20150303");
  5. stringDateTimes.Add("20161215");

foreachでループさせて出力

  1. foreach (string stringDateTime in stringDateTimes)
  2. {
  3.     Console.WriteLine(DateTime.ParseExact(stringDateTime, "yyyyMMdd", null));
  4. }

Selectメソッドで取り出した結果をLambdaでDateTimeのコレクションに変換してから出力

  1. IEnumerable<DateTime> dateTimes = stringDateTimes.Select(
  2.     (string stringDateTime) =>
  3.     {
  4.         return DateTime.ParseExact(stringDateTime, "yyyyMMdd", null);
  5.     }
  6. );
  7. foreach (DateTime d in dateTimes)
  8. {
  9.     Console.WriteLine(d.ToString());
  10. }

Selectメソッドで取り出した結果をLambdaでDateTimeのコレクションに変換してから出力(varを使ったり、式形式のLambdaに変えたりして記述)

  1. var dateTimes2 = stringDateTimes.Select(
  2.     (string stringDateTime) => DateTime.ParseExact(stringDateTime, "yyyyMMdd", null)
  3. );
  4. foreach (DateTime d in dateTimes)
  5. {
  6.     Console.WriteLine(d.ToString());
  7. }

ForEachメソッドで繰り返し、LambdaでDateTimeに変換と出力

  1. stringDateTimes.ForEach(
  2.     (string stringDateTime) =>
  3.     {
  4.         DateTime d = DateTime.ParseExact(stringDateTime, "yyyyMMdd", null);
  5.         Console.WriteLine(d.ToString());
  6.     }
  7. );

Lambdaはいつも混乱するからメモ代わりに。

TeraPadの色設定(自分用)

下記を拡張子「.tpc」で保存して、TeraPadに読み込ませて使う。

  1. [Color]
  2. Font=clWindow
  3. Bk=clWindowText
  4. SelStr=clHighlightText
  5. SelBk=clHighlight
  6. Ret=clTeal
  7. Tab=clSilver
  8. Link=clAqua
  9. Inyou=$00FF8000
  10. UnderLine=clSilver
  11. WrapLine=clBtnFace
  12. ColLine=$00E4E4E4
  13. LeftBarStr=clWindow
  14. RulerStr=clWindowText
  15. Html_Comment=clSilver
  16. Html_Asp=$00C08000
  17. Html_Tag=$00FF8000
  18. Html_Option=clGreen
  19. Perl_Comment=clSilver
  20. Perl_Str=clOlive
  21. Perl_KeyWord=clTeal
  22. Perl_KeyWord_Bold=1
  23. Perl_KeyWord_Case=1
  24. Php_Comment=clSilver
  25. Php_Str=clOlive
  26. Php_KeyWord=clTeal
  27. Php_KeyWord_Bold=1
  28. Php_KeyWord_Case=0
  29. Css_Comment=clSilver
  30. Css_Str=clOlive
  31. Css_KeyWord=clTeal
  32. Css_KeyWord_Bold=1
  33. Css_KeyWord_Case=0
  34. Ruby_Comment=clSilver
  35. Ruby_Str=clOlive
  36. Ruby_KeyWord=clTeal
  37. Ruby_KeyWord_Bold=1
  38. Ruby_KeyWord_Case=1
  39. Ini_Comment=clSilver
  40. Ini_Sec=$00FF8000
  41. Ini_Key=$00FF8000
  42. Bat_Comment=clSilver
  43. Bat_Label=$00FF8000
  44. Bat_KeyWord=clTeal
  45. Bat_KeyWord_Bold=1
  46. Bat_KeyWord_Case=0
  47. Cpp_Comment=clSilver
  48. Cpp_Str=clOlive
  49. Cpp_KeyWord=clTeal
  50. Cpp_KeyWord_Bold=1
  51. Cpp_KeyWord_Case=1
  52. Java_Comment=clSilver
  53. Java_Str=clOlive
  54. Java_KeyWord=clTeal
  55. Java_KeyWord_Bold=1
  56. Java_KeyWord_Case=1
  57. Js_Comment=clSilver
  58. Js_Str=clOlive
  59. Js_KeyWord=clTeal
  60. Js_KeyWord_Bold=1
  61. Js_KeyWord_Case=1
  62. Vb_Comment=clSilver
  63. Vb_Str=clOlive
  64. Vb_KeyWord=clTeal
  65. Vb_KeyWord_Bold=1
  66. Vb_KeyWord_Case=0
  67. Hsp_Comment=clSilver
  68. Hsp_Label=$00FF8000
  69. Hsp_Str=clOlive
  70. Hsp_KeyWord=clTeal
  71. Hsp_KeyWord_Bold=1
  72. Hsp_KeyWord_Case=0
  73. Pas_Comment=clSilver
  74. Pas_Str=clOlive
  75. Pas_KeyWord=clTeal
  76. Pas_KeyWord_Bold=1
  77. Pas_KeyWord_Case=0

C# 文字列比較

  • ググった

C#の文字列比較はどの方法が良いのか分からなかったので、「C# 文字列比較」で検索すると、上位に出てくるのがケース1~3だった。

 

ケース1

  1. string stringValue = "0";
  2. int intValue = 0;
  3.  
  4. // stringとintを==演算子で比較するとビルドエラーになる。
  5. System.Console.WriteLine(stringValue == intValue);

「等値演算子で比較すればビルドエラーになるから、実行前に発見出来る」というのをネットで見かけた。そして、その多くは「で、ビルドエラーはどのように対処するのか」が抜けていた。

 

ケース2

  1. string stringValue = "0";
  2. object objectValue = "0";
  3.  
  4. System.Console.WriteLine(stringValue == objectValue);

 

ケース3

  1. string stringValue = "0";
  2. object objectValue = 0;
  3.  
  4. System.Console.WriteLine(stringValue == objectValue);

 

ケース2~3は、ボックス化してしまえば、intとstringは比較出来てしまい、ケース1のように等値演算子だけでは抜けがあるというのを示している。
これもネットで見かけたけど、どうすれば良いのかという答えにはならないかな。モヤッとしたまま。

  • MSDNライブラリを確認

そもそもstringとは?と思ってMSDNライブラリで調べてみた。

string (C# リファレンス)

比較的最初の方でこのように書いてある。

string は参照型ですが、等値演算子 (== および !=) は、string オブジェクトの参照ではなく、値を比較するように定義されます。

 

 そして、下記のサンプルコードが付属している。 

  1. string a = "hello";
  2. string b = "h";
  3. // Append to contents of 'b'
  4. b += "ello";
  5. Console.WriteLine(a == b);
  6. Console.WriteLine((object)a == (object)b);

実行してみるとstring同士での比較(5行目)はTrue、object型にキャストしたstring同士の比較(6行目)はFalseになる。

string(System.Stringクラス)は、等値演算子で比較した場合、違うオブジェクトでもTrueと判定してくれるという事だ。

 

  • 何故、等値演算子で 比較出来るのか?

「オブジェクトの比較だと思っていたのに、文字列自体を比較してくれるのは何故か」と気になって調べていたら、このページを見つけた。

String.Equality 演算子 (String, String) (System)

 stringは等値演算子オーバーロードをしていて、解説にはこう書いている。

The operator, in turn, calls the static Equals(String, String) method, which performs an ordinal (case-sensitive and culture-insensitive) comparison.

(大文字小文字を区別し、カルチャーに依存しない比較を行う、staticのEquals(String, String)を呼び出します。)

 

つまり、下記4行目と5行目は同じという事になる。

  1. string stringValue1 = "0";
  2. string stringValue2 = "0";
  3.  
  4. System.Console.WriteLine(stringValue1 == stringValue2);
  5. System.Console.WriteLine(string.Equals(stringValue1, stringValue2));

 

  • 結論

上記を踏まえて、

  1.  文字列比較する時はToStringなどで型を合わせてから行う。
    (ビルドエラーで云々とか言わないで型を合わせる。当たり前だけど。)
  2. 等値演算子String.Equals メソッド (String, String) (System)を呼び出しているので使用して構わない。
    (String.Equals メソッド (String) (System)のどちらを使うかはプロジェクトごとに決めるのが良いかも。)

という、個人的な結論に至った。

 

  • 補足

String.Equality 演算子 (String, String) (System)のメモには、こんな事が書いてあった。

The Visual Basic compiler does not resolve the equality operator as a call to the Equality method.

(Visual Basic コンパイラは等値演算子で Equality メソッドを呼び出しません。)
The operator, in turn, calls the static Equals(String, String) method, which performs an ordinal (case-sensitive and culture-insensitive) comparison.

(代わりに、 Operators.CompareString メソッドが呼ばれます。)

VBでは等値演算子で呼ばれるのはString.Equalityではなく、Operator.CompareStringを呼び出しているという事だ。

  1. Option Compare Text
  2.  
  3. Class Program
  4.     Public Shared Sub Main()
  5.         Dim stringValue1 As String = "aaa"
  6.         Dim stringValue2 As String = "AAA"
  7.  
  8.         System.Console.WriteLine(stringValue1 = stringValue2)
  9.         System.Console.WriteLine(stringValue1.Equals(stringValue2))
  10.     End Sub
  11. End Class

 「Option Compare Text」が、ソースまたはプロジェクトに宣言してある状態で等値演算子で比較すると、大文字小文字の違いがあってもTrueが返ってきてしまう(8行目)。

VBを知っていれば当然といえば当然の事だろうけど、C#と同じ感覚でコードを書いてしまうとハマってしまうかもしれない。

Postfixとclamav-milterの連携で「Permission denied」と出た時の対処

Postfixclamav-milterを連携させた時に「Permission denied」と出る。

  1. Apr 12 12:26:11 nakamurakko postfix/smtpd[23212]: warning: connect to Milter service unix:/var/run/clamav/clamav-milter.sock: Permission denied

その場合、は下記のように設定している。

 

「/etc/clamav-milter.conf」を開きソケットファイルの権限を変えて、同一グループからもソケットファイルを参照出来るようにする。

  1. MilterSocketMode 660

 

clamav-milterの起動グループユーザーにPostfixを参加させる。

  1. gpasswd -a postfix clam (※1)

 

clamav-milterを再起動。

  1. service clamav-milter restart

 

「/var/log/maillog」 に「Permission denied」が出なくなれば完了。
clamav-milterの起動スクリプトを編集する手順を載せているのは結構あるけど、この手順の方が安全な気がする。

 

※1…参加させるグループは「clamav」の場合もあるので、ソケットファイルのグループを確認する。