No more Death March

あるSEのチラシの裏 C# WPF

WPF StackPanelとScrollVIewerの不思議な動き

StackPanelとScrollViewerを組み合わせた時、
思った通りにScrollViewerが機能せずに引っ掛かることがある。

普通に動く場合

f:id:nomoredeathmarch:20190120174203p:plain

StackPanelをScrollViewerでラップすると、
画像のようにスクロール操作が出来るようになる。

機能しない場合

f:id:nomoredeathmarch:20190120174207p:plain

先の例とほとんど変わらないのだが、
ScrollViewerの親要素の大きさがAutoで指定されていると、
スクロールバーが見切れた状態になりスクロール操作が出来ない。

じゃあどうする?

f:id:nomoredeathmarch:20190120174211p:plain

ScrollViewerのサイズが特定出来るよう、Autoではなく*で高さを指定するとスクロール操作が出来るようになる。

なにが原因なのか?

はっきりとした理由は不明、なので予想だけ。

StackPanelは列挙する方向に対して無限長の領域をとると思われる。
ので、ScrollViewerでラップしてやるとStackPanelから返ってくる要求サイズは無限長となりScrollViewerの要求サイズも無限長となる。

本来であれば更に親の要素に要求サイズを渡したとき、
親の要素が具体的に決まっていればその大きさに合わせてScrollViewerのサイズも調整されるが
GridのAuto指定の場合だと子要素のサイズが採用されるのでScrollViewerとStackPanelのサイズは不定となる。

結果、画面には収まらないが要求された通りの描画が行われScrollViewerから見ても子要素が全部収まってるという判断がされるので、
スクロール操作は出来ないよね、という具合に判断されている。

MSDNによるとRowDefinition.Heightプロパティの既定値はstarなので、
最初のサンプルと最後のサンプルではWindowの高さに合わせた調整が行われ、
ScrollViewerのサイズもレイアウト時に調整されたのだろう。

docs.microsoft.com

つまり、ScrollViewerが自分の高さがわからないという状況にならなければ良いわけで、
ScrollViewerのMaxHeightプロパティに値を指定してもスクロールバーは動くようになる。
が、動的に配置を調整してくれるXAMLの強みが無くなってしまうのでこの方法は微妙だと思う。


StackOverFlowでも「ScrollViewerが上手く動かない」というトピックが散見され、
みんな同じとこで嵌ってる様子。

この辺りは仕様上の挙動と直感的な挙動が剥離してるよなぁ。。。

WPF 不明な型 ~ を作成できません。

XAML関係で引っ掛かりやすいところ。

XAMLの中で名前空間を指定する際に、同じアセンブリ内であればアセンブリ名を省略して記述することが出来る。

次の画像がアセンブリ名を省略したもの。
f:id:nomoredeathmarch:20190114164547p:plain

省略せずに記述したものは次の画像のようになる。
f:id:nomoredeathmarch:20190114164551p:plain

省略した方が記述が短いので良いのだが、
前者のディクショナリを外部のアセンブリから利用し、その名前空間内で宣言された型を生成しようとすると。

不明な型 ~ を作成できません。

と実行時に例外が出てしまう。

後者のようにアセンブリ名を省略しなければ例外は発生せず、ちゃんと動いてくれる。
XAMLはパースされた側が解決出来るか否かではなく、パースした側が解決出来るかどうかという問題ということだろうか???
(その割にはリソースディクショナリをマージする記述は相対パスだけでも大丈夫だったりするのだけど・・・)

WPF 外部ライブラリに宣言されているリソースディクショナリの使用方法

いつも書き方を忘れるのでメモ。

下の画像のようにプロジェクトResourceDictionaryServe内にリソースディクショナリMyResourceDictionaryがあったとして
f:id:nomoredeathmarch:20190113230301p:plain

これをResourceDictionaryClientで使いたいとなった場合の記述は以下の通り
f:id:nomoredeathmarch:20190113230305p:plain

ResourceDictionaryのSourceプロパティに
"/{アセンブリ名};component/{リソースディクショナリ名}"
と記述する。

StackOverFlowExceptionの原因

忘れないうちにもう一つ、

ほとんどが記述ミスによるメソッドやプロパティの無限ループが原因だが、
Linqの遅延評価が多用されている場合も呼び出し時に発生することがある。

自分の場合、かなり頻度の高い処理の中で
同じ変数にLinqの遅延評価される戻り値を代入、
実行時まで大量のデリゲートが積み上がり呼び出し時に例外が発生していた。

無限ループと同様ただの記述ミスなのだけどレアケースなのでメモ。

WPFのVisibilityとIsVisibleの違い

twitterではぶつぶつ独り言を呟いているけど、
ブログの方は半年以上なにも書いてなかったのか・・・

ここしばらくWPFやC#で色々と痛い思いをしてるので、
忘れないように少しずつブログに吐き出す習慣をつけるようにしたい。

ソースやXAMLも載せた方が良いのだろうけど、
手間かけるとどうせ飽きるので適当に書きます。

で、掲題の話、
以前この違いをはっきり意識せずにコーディングしてて成大にバグったのでメモ

端的にいうと

Visibility→この要素の可視状態

IsVisible→この要素が表示されているか否か

ということ。

WPF ボタンのカスタマイズ方法概略

はじめに

入力に使うコントロールは結構癖があり忘れやすいのでメモしておきます。

仕事で使う場合だと色を変えたいとかアイコン埋め込みたいとか、
色々あるかと思いますがあくまで雰囲気重視の記述です。

ボタン側のXAML

ボタンのXAML、Buttonクラスを継承して記述、コードビハインドは無し。
VSでユーザーコントロールWPF)を追加して派生元をUserControlからButtonに変更したものを編集

<Button x:Class="WpfApp1.CustomButton"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800"
             OverridesDefaultStyle="True"
             FocusVisualStyle="{x:Null}"
             >
    <Button.Template>
        <ControlTemplate TargetType="Button">
            <Grid>
                <Border
                    x:Name="ButtonVisual"
                    BorderBrush="DimGray"
                    BorderThickness="2"
                    Background="AliceBlue"
                    >
                    <ContentPresenter
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        Content="{TemplateBinding Content}"
                        />
                </Border>
                <Border
                    x:Name="MaskOnPressed"
                    Background="Black"
                    Opacity="0.1"
                    Visibility="Hidden"
                    />
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger Property="IsFocused" Value="true">
                    <Setter TargetName="ButtonVisual" Property="BorderBrush" Value="MidnightBlue"/>
                </Trigger>
                <Trigger Property="IsMouseOver" Value="true">
                    <Setter TargetName="ButtonVisual" Property="Background" Value="Lavender"/>
                </Trigger>
                <Trigger Property="IsEnabled" Value="false">
                    <Setter Property="Opacity" Value="0.5"/>
                </Trigger>
                <Trigger Property="IsPressed" Value="true">
                    <Setter TargetName="MaskOnPressed" Property="Visibility" Value="Visible"/>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>
    </Button.Template>
</Button>

画面側のXAML

ボタンを画面に貼っつけたXAML、こちらもコードビハインドなし。

<Window x:Class="WpfApp1.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:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Canvas>
        <local:CustomButton
            Canvas.Left="10"
            Canvas.Top="10"
            Width="100"
            Height="20"
            Content="ボタン1"
            />
        <local:CustomButton
            Canvas.Left="10"
            Canvas.Top="40"
            Width="100"
            Height="20"
            Content="ボタン2"
            />
        <local:CustomButton
            Canvas.Left="10"
            Canvas.Top="70"
            Width="100"
            Height="20"
            Content="ボタン3"
            IsEnabled="False"
            />
    </Canvas>
</Window>

実行時イメージ

上から順に、
フォーカス取得中
フォーカス無しの表示
Disable(IsEnabled=false)の表示

f:id:nomoredeathmarch:20180522231245p:plain

ポイント

標準スタイルの無効化

・OverridesDefaultStyle=”true”で標準のスタイルを読み込まないように
・FocusVisualStyle="{x:Null}"でフォーカス取得時スタイルを初期化
 (フォーカス取得時に点線が表示されるやつ。)

トリガーで実現しているもの

・フォーカス時に外枠の色を濃い目に変更
・マウスオーバー時に塗りつぶし色を濃い目に変更
・Disableの時、全体を半透明にして使えないっぽい外観に
・押下時、半透明のフィルタを被せて操作に対するレスポンスを表現

WPF 描画の練習 マル印

前回の記事に引き続き、WPFの描画周りを練習

前回の記事はこちら↓
nomoredeathmarch.hatenablog.com

前回はバツ印を作ったので今回はマル印を作ってみました。
まだ簡単・・・

イメージ

f:id:nomoredeathmarch:20180404225738p:plain

XAML

<UserControl x:Class="WpfApp1.UserControl2"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfApp1"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Canvas
        Height="100"
        Width="100"
        Background="Aqua"
        >
        <Path
            Stroke="Black"            
            >
            <Path.Fill>
                <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
                    <GradientStop Color="Tomato" Offset="0"/>
                    <GradientStop Color="Red" Offset="0.5"/>
                    <GradientStop Color="Crimson" Offset="0.7"/>
                </LinearGradientBrush>
            </Path.Fill>
            <Path.Data>
                <CombinedGeometry
                    GeometryCombineMode="Xor"
                    >
                    <CombinedGeometry.Geometry1>
                        <EllipseGeometry Center="50,50" RadiusX="50" RadiusY="50"/>
                    </CombinedGeometry.Geometry1>
                    <CombinedGeometry.Geometry2>
                        <EllipseGeometry Center="50,50" RadiusX="30" RadiusY="30"/>
                    </CombinedGeometry.Geometry2>
                </CombinedGeometry>
            </Path.Data>
        </Path>
    </Canvas>
</UserControl>

ポイント

・半径が違う円を重ねて合成
・GeometryCombineModeはXorでGeometry2の領域を描画しない

ジオメトリの使い方、良いサンプルないかなぁってネットで検索したりしてたのですが、
手元にあるエッセンシャルWPFの204ページ辺りにしっかり書いてありました。

エッセンシャルWPF:Windows Presentation Foundation (Programmer's SELECTION)

エッセンシャルWPF:Windows Presentation Foundation (Programmer's SELECTION)