「Androidは電気羊の夢を見るか」を読みたい管理者のブログ

仕事などでの色々な発見を記事にしてます。不定期更新。

ContentControl経由でViewを呼び出すとViewModelが変わっちゃうらしい

かつてこんな記事を書きました。
bignight.hatenablog.com
それには例外がありました。
ContentControl経由でViewを呼び出すとViewModelを共有してくれない😱
さあどうする?というわけで実験してみました。
(ちなみにContentControl経由で子コントロールを呼ぶ方法は以下の記事を参考にしてください。今回はDataTemplate経由で子コントロールを呼びます。)
bignight.hatenablog.com

簡単なコードです
Viewはこんな感じ

<Window x:Class="WpfApp1.Window1"
        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="Window1" Height="450" Width="800">
    <Window.Resources>
        <DataTemplate x:Key="originalTemplate">
            <Border BorderBrush="Orange"  BorderThickness="1">
                <local:myControl/>
            </Border>
        </DataTemplate>
        <local:myControlViewModel x:Key="myControlViewModel"/>
    </Window.Resources>
    <StackPanel>
        <TextBlock Text="Content:windowViewModel"/>
        <!--ContentControl経由でViewを呼ぶ。ContentはwindowViewModelとする。-->
        <ContentControl Content="{StaticResource myControlViewModel}" ContentTemplate="{StaticResource originalTemplate}"/>
        <TextBlock Text="Content:None"/>
        <!--ContentControl経由でViewを呼ぶ。Contentは指定しない。-->
        <ContentControl ContentTemplate="{StaticResource originalTemplate}"/>
        <TextBlock Text="Content:Binding"/>
        <!--ContentControl経由でViewを呼ぶ。Contentは{Binding}とする。-->
        <ContentControl Content="{Binding}" ContentTemplate="{StaticResource originalTemplate}"/>

        <TextBlock Text="{Binding Text}"/>

    </StackPanel>
</Window>

コードビハインドでViewModelを指定します。

    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            DataContext = new Window1ViewModel();
        }
    }

メインのWindowのViewModelの中身

    public class Window1ViewModel
    {
        public string Text { get; set; } = "これはWindow1ViewModel";
    }

子コントロールはこんな感じ
TextBlockのTextにTextをバインドさせてみます。

<UserControl x:Class="WpfApp1.myControl"
             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"
             Name="mine">
    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Binding:"/>
            <TextBlock Text="{Binding}"/>
        </StackPanel>
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Binding Text:"/>
            <TextBlock Text="{Binding Text}"/>
        </StackPanel>
    </StackPanel>
</UserControl>

子コントロールのViewModel(最初にStaticResourceで指定したやつ)

    public class myControlViewModel
    {
        public string Text { get; set; } = "これはmyControlViewModel";
    }

私の当初の予想では「"これはWindow1ViewModel";」が3回表示されると思ったんですが、結果は違いました。
実行結果はこんな感じ
オレンジの枠で囲んであるところが子コントロールです。

Content="{Binding}" と記述したときだけViewModelの共有が行われています。

どうやらContentControlのViewModelはContentに何を指定したかに依存するようですね。
ContentControlを継承したButtonなんかどうなるんだろう。
まだまだ知らないことが沢山あります。

なんでPanelにはItemsSourceを指定できないの?

WPFの小ネタ
やっぱりWPFは使ってて面白い技術だと思うわけですよ。
使いこなせるかはともかく。
今回は最近発見したネタじゃないのであっさりいきます。

結論から述べるとListBoxはItemsControlを継承したクラスなのに対してPanelはPanelなんですね。
何が違うかというとPanelはXAML内でアイテム(UIElement)を配置するのに対してListBoxはItemsSource(ViewModelが持つデータ)を指定するのが大きな違い。
でも似てるんですよね。要素を並べるという意味で。

最近よく訪問するこのサイト
grabacr.net
によると、ItemsSourceの中にPanelを持つ。
ItemsControlのItemsPanelプロパティなんですが、ItemsControlの持つデータの配置を指定できちゃう。

PanelとItemsControlの実装上の違いはこんな感じ

<StackPanel>
   <TextBlock Text="テキスト1">
   <TextBlock Text="テキスト2">
</StackPanel>
<ListBox ItemsSource ={Binding Items}/>

StackPanelがXAML上で子要素を指定してるのに対してListBoxがViewModelのデータをBindingしてるのがわかります。

なんでこんなこと疑問に思ったんだろうと、思いだしてみるに、ItemsControlにListBox風スタイルが適用できないかなーって思ったのがきっかけでした。
ListBoxだと選択状態とか出てきて面倒くさいのでStyleを継承したいなーと。
違うんですよね。ListBoxからListBoxの機能を使えなくすればいい。ついでに言ってしまうとListBoxも色々カスタムできてしまう。

このことを発見した当時、深い感動を覚えたのを覚えてます。その感動はいつしか「当たり前のこと」になって、感動したことさえ忘れてしまう。

だから人々は今日も日記を書き続けるんだと思います。

WPFは楽しい。使いこなせれば楽しい。得意とか、ニッチとかじゃなく、楽しいからやりたい。ただそれだけです。

ContentControlのDataTemplateネタが少なくて少しさびしい

ご無沙汰しております。ここは本来技術ネタを書く場所だったんですが、方向がずれてきてまた技術に返り咲こうかなと。
昔の記事を見てると色々やってるもんですね。ボタンを動的に配置したい、トリガーでStyleを変更したい、アニメーション云々。
久しぶりにWPFに戻ってまいりました。
昔に比べてDataTemplateの記載は増えた気がする。現場では当たり前のように使われるDataTemplate。動的配置だいだいだいすき。
でも気になることがあるんですよね。DataTemplateの記事を見てるとItemsControl(ListBoxでもいいが)の記載ばかり目に入る。
そうじゃないんだ、DataTemplateはContentPresenterでも使える重要な要素なんだ、というのをね。もっと強調してもいいのかな、と思いこの記事を書きます。

続きを読む

sqlplusでsysdbaでログインできない

oracleの勉強を始めました。
そこで12c リリース2をインストールしました。
しかしsql plusからsqlplus / as sysdbaを実行したら以下のようなエラーメッセージが出てきました。

ORA-01017: invalid username/password; logon denied

ORA-12560: TNS: プロトコル・アダプタ・エラーが発生しました

なんてことはない、データベースを作ってなかっただけです。
以下を参考にデータベースを作成したら解消されました。
docs.oracle.com

git resetとgit revertの違いなど

昨今gitはGUIツールで使う人が多いと思いますが、コマンドレベルまでレイヤーを下げることでかゆい所に手が届いたりするんですね。
たとえば今やってる作業を一時的に保管しておきたいなどgit stashが便利です。これはコミットするほどでもない作業をブランチを変えたいなど止む終えない理由で残しておきたい時なんかに使います。

ところで最近知った機能にgit resetとgit revertの違いなんかがあります。両者は似たような機能なのでGUIツールなんかでは片方しかサポートしてないことがままありますが、この違いを知っとくとGUIツールを使うときなんかに何かといいかもです。

さてgit resetとgit revertの違いですが、両者ともコミットの状態をもとに戻したいという意味では同じです。ただresetが履歴を残さないで変更を巻き戻すのに対して、git reverの方は履歴はそのままで変更を戻すコミットを作成するという違いになります。

だったらgit resetだけあればいいじゃんか、って思うのですが、バージョン管理の性質上そうはいかないのです。
git revertは戻したいコミットも残した上で変更を元に戻したコミットを上に重ねるという意味になります。
なんでこんな回りくどい方法を取っているかというと、リモートリポジトリに変更の履歴が反映されてて他の人がそれを参照してたりすると大変なことになるので、極力コミットログを変更したくない時にrevertを使います。そうすれば今までの履歴に変更を巻き戻したという履歴が加わってそれがコミットされます。

人間の直観的にはresetのほうが正しい動きなんですけどね。

ちなみにGitHubDesktopでrevertを行うと戻したという履歴が残って、VisualStudioのチームエクスプローラーを使うとresetが内部的に呼ばれるため戻したという履歴は残りません。

git小ネタでした。

【改訂新版】Gitポケットリファレンス

【改訂新版】Gitポケットリファレンス

入門git

入門git

既に別のところに移動させてあるAppDataのディレクトリを別のところに移動させたい

AppDataを別ドライブに移すってエントリは多く見かけるんですが、すでにあるのを移動させたいってのがあまりなかったので

ここでは別ドライブへのシンボリックリンクを貼ってあるという前提で進めます。


①念のためAdominユーザでログインします(移動させたいappdataとは別ユーザでWindowsにログインします。)
②AppDataの中身を別のドライブにコピーします。
 コピーしてぺ。このエントリを読んでる方は一度AppDataの場所移動を経験済みなはずなので詳細は省略します。
③既にあるリンクを削除します
cmdを立ち上げてcdで移動してCドライブのAppDataが置いてあるディレクトリに移動します。
えーとAdominで立ち上げるんだったかな
そこには既にシンボリックリンクとしてのAppDataがあるはずなのでdirコマンドを実行して  AppData が表示されることを確認します。

rmdir AppData

と打ちます
AppDataのシンボリックリンクが消えます。
これを実行したからといってAppDataのデータ本体が消えるわけではないのでご安心を。

ポイントとして

  • 作業ディレクトリがCドライブのAppDataへのシンボリックリンクが張られているディレクトリであること
  • dirコマンドを実行して  AppDataと表示されること 〈SIMLINK〉はファイルへのショートカットであり〈SIMLINKED〉はフォルダへのショートカットらしいです。
  • rmdirを実行してもAppDataの中身本体が削除されるわけではないこと

シンボリックリンク(ぶっちゃけショートカット)を作成します。
 コマンドに 

mklink /d AppData 移したい場所のパス

と入力します。
 ここでのポイントは

  • mklinkのオプション/d

これ書かないとファイルへのリンクと見なされ失敗します。
わたしはこれでハマりました。えぇ。

以上。