Livetを使ったC#のWPFプログラミング

システムとして機能が網羅されたサンプルをもとに、Livetを使ったC#のWPFシステムの開発技能を身に付ける為の学習サイト


8.画面遷移


MVVMの画面遷移

MVVMのWPFの画面遷移の基本(基礎)は下記の「画面遷移の骨格」を読んで理解してから、これからの解説を読んで下さい。 (ソースのダウンロードが出来ます)

画面遷移の骨格


画面


MainWindowでログインボタンを押してWindow2ヘ画面遷移する例のXAMLは下記のようになります。


View
<Window x:Class="LivetWPFApplication100.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
        xmlns:l="http://schemas.livet-mvvm.net/2011/wpf" 
        xmlns:v="clr-namespace:LivetWPFApplication100.Views"
        xmlns:vm="clr-namespace:LivetWPFApplication100.ViewModels"       
        xmlns:b="clr-namespace:LivetWPFApplication100.Behaviors"       
        Title="MainWindow" Height="714" Width="1035" 
        WindowStartupLocation="CenterScreen">

    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/Resources/Styles.xaml"/>
                <ResourceDictionary Source="/Resources/Styles2.xaml"/>
                <ResourceDictionary Source="/Resources/StylesBG.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

    <i:Interaction.Triggers>
        <!--WindowのContentRenderedイベントのタイミングでViewModelの
        Initializeメソッドが呼ばれます-->
        <i:EventTrigger EventName="ContentRendered">
            <l:LivetCallMethodAction MethodTarget="{Binding}" 
            MethodName="Initialize"/>
        </i:EventTrigger>
        
        <l:InteractionMessageTrigger 
            MessageKey="MessageKey2" Messenger="{Binding Messenger}">
            <l:TransitionInteractionMessageAction
                WindowType="{x:Type v:Window2}" Mode="Modal"/>
        </l:InteractionMessageTrigger>
    </i:Interaction.Triggers>

    <i:Interaction.Behaviors>
      <b:CloseButtonBehavior IsWindowCloseEnable="{Binding ElementName=CloseCheck, 
      Path=IsChecked}"/>
    </i:Interaction.Behaviors>

    <Grid  Background="#FFE6E6FA">

      <Label Background="AliceBlue" Content="ユーザーID" Height="22" 
          HorizontalAlignment="Left" Margin="381,259,0,0" Name="label1" 
          VerticalAlignment="Top" Width="70" />
      <Label Background="AliceBlue" Content="パスワード" Height="22" 
          HorizontalAlignment="Left" Margin="381,287,0,0" Name="label2" 
          VerticalAlignment="Top" Width="70" />

      <TextBox InputMethod.PreferredImeState="Off" 
          Style="{StaticResource HighlightTextBox}" Height="22" 
          HorizontalAlignment="Left" Margin="450,259,429,0" Name="txtユーザーID"  
          Text="{Binding Path=txtユーザーID}" VerticalAlignment="Top" Width="134"/>

      <PasswordBox   MaxLength="10"   Height="22" HorizontalAlignment="Left" 
          Margin="450,287,0,0" VerticalAlignment="Top" Width="134">
          <i:Interaction.Behaviors>
              <b:PasswordBindBehavior Password="{Binding txtパスワード, 
              Mode=TwoWay}" />
          </i:Interaction.Behaviors>
      </PasswordBox>

      <Button Style="{StaticResource CommandTemplate}" Content="ログイン" 
          Width="130" Height="30"
          Command="{Binding GotoCommand22}"
          HorizontalAlignment="Center" VerticalAlignment="Center" 
          Margin="450,322,433,323" Foreground="White" />

      <v:UserControlFooterN1  Margin="0,0,0,0" HorizontalAlignment="Left" 
      VerticalAlignment="Bottom"  />

    </Grid>
</Window>

Windows フォームでは、IME (かな漢字変換) を制御するためにコントロールごとに ImeMode というプロパティが用意されていましたが、WPF の TextBox には、ImeMode プロパティがありません。

WPF では 入力方法を制御するために InputMethod というクラスが用意されています。51行目の「InputMethod.PreferredImeState="Off" 」は、IME を無効にします。


MainWindow.xamlとMainViewModel.csの紐付けは、MainWindow.xamlのコードビハインドファイルであるMainWindow.xaml.csのコンストラクタに「this.DataContext = new MainViewModel();」と記述します。


ViewとViewModelの紐付け
public MainWindow()
{
   InitializeComponent();
   this.DataContext = new MainViewModel();
}


ViewのMainWindowのユーザーID(51~54行)とパスワード(56~62行)はMainViewModelでは変更通知プロパティとして設定する必要があります。

txtユーザーIDとtxtパスワードのプロパティのViewModelでの処理(実装)が下記の変更通知プロパティです。

変更通知プロパティ


ViewModel
 #region txtユーザーID変更通知プロパティ
   public string txtユーザーID { get; set; }
   public string txtパスワード { get; set; }
 #endregion


ZAMLのログインのButton(64~68行)をクリックした時の処理(命令)はCommandと呼ばれています。このCommandはViewModelの「GotoCommand22」メソッドと紐付け(Binding)されています。

MainWindowのViewModelであるMainViewModelは下記のようになります。


GotoCommand22(ViewModel)
  public ViewModelCommand GotoCommand22
 {
   get { return new Livet.Commands.ViewModelCommand(Goto22); }
 }
 public void Goto22()
 {
   if (txtユーザーID == null)
   {
     MessageBox.inWShow("ユーザーIDが未入力です");
     return;
   }
   if (txtパスワード == null)
   {
      MessageBox.Show("パスワードが未入力です");
      return;
   }            
   if (!作業員マスターログインチェック(txtユーザーID.ToUpper(), txtパスワード))
   {
      MessageBox.Show("ユーザーIDが違うかパスワードが違います");
      return;
   }
            
      Messenger.Raise(new TransitionMessage(new ViewModel2() 
      { NeedHideOwner = true }, "MessageKey2"));
   }

作業員マスターログインチェック
#region 作業員マスターログインチェック
  public bool 作業員マスターログインチェック(string str作業員コード, 
  string strパスワード)
  {
    bool bRet = false;

    using (OracleConnection con = new OracleConnection())
    {

       con.ConnectionString = "User Id=UserId; Password=Password; 
       			Data Source=DnsName;Pooling=false;";
                con.Open();
                //

                string strSQL = String.Format("SELECT COUNT(*) AS CNT FROM 
                作業員マスター Where 作業員コード = '{0}' AND パスワード = '{1}'", 
                str作業員コード.ToUpper(), strパスワード);

                OracleCommand oracmd = new OracleCommand(strSQL, con);
                OracleDataReader reader = oracmd.ExecuteReader();

                //*** Tran ***
                //tx.Commit();
                while (reader.Read() == true)
                {
                    if (Convert.ToInt16(reader["CNT"].ToString()) > 0)
                    {
                        bRet = true;
                    }
                }
                return bRet;
            }
        }
        #endregion

UserId、Password、DnsNameは実際のものに置き換えてください。str作業員コード.ToUpper()は、コードを大文字にしています。


遷移先のWindow2のXAMLは下記のようになります。太字部分(LoadedやClosing)はMVVMパターンでは有名な Expression Blend SDK に含まれる System.Windows.Interactivity.dll のクラス (EventTrigger, InvokeAction, Interaction 等)を使用してWPFのコントロールのイベントとViewModel等で公開される ICommand を実装したコマンドプロパティをコードビハインドファイルやハンドラを使用せずにxaml上で関連付ける方法 でSystem.Windows.Interactivity名前空間の EventTrigger クラスの EventName に コマンドと関連付けるイベントを設定し、 InvokeCommandAction によりコマンドとの関連づけを行っています。 ListBoxでは、EventTriggerのEventNameとしては「MouseEnter」、「MouseLeave」、「SelectionChanged」などがあります。

[WPF] コントロールの任意のイベントとコマンド xaml上で関連付ける
View
<Window x:Class="LivetWPFApplication100.Views.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"         
        xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
        xmlns:v="clr-namespace:LivetWPFApplication100.Views"        
        xmlns:b="clr-namespace:LivetWPFApplication100.Behaviors"
        
        Title="Window2" Height="714" Width="1035" 
        WindowStartupLocation="CenterScreen">

    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/Resources/Styles.xaml"/>
                <ResourceDictionary Source="/Resources/Styles2.xaml"/>
                <ResourceDictionary Source="/Resources/StylesBG.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>
    
    <i:Interaction.Triggers>
        <!--WindowのContentRenderedイベントのタイミングでViewModelのInitialize
        メソッドが呼ばれます-->
        <i:EventTrigger EventName="ContentRendered">
            <l:LivetCallMethodAction MethodTarget="{Binding}" 
          MethodName="Initialize"/>
        </i:EventTrigger>
        <l:InteractionMessageTrigger 
            MessageKey="MessageKey3" Messenger="{Binding Messenger}">
            <l:TransitionInteractionMessageAction
                WindowType="{x:Type v:Window3}" Mode="Modal"/>
        </l:InteractionMessageTrigger>

        <i:EventTrigger EventName="Loaded">
            <i:InvokeCommandAction Command="{Binding Path=Loaded}" 
              CommandParameter="{Binding Mode=OneTime,
         RelativeSource={RelativeSource Mode=FindAncestor,
          AncestorType={x:Type Window}}}"/>
        </i:EventTrigger>

        <i:EventTrigger EventName="Closing ">
            <i:InvokeCommandAction Command="{Binding Path=Closing}"
            CommandParameter="{Binding Mode=OneTime,
            RelativeSource={RelativeSource Mode=FindAncestor,
            AncestorType={x:Type Window}}}"/>
        </i:EventTrigger>

        <l:InteractionMessageTrigger MessageKey="Close" 
          Messenger="{Binding Messenger}">
            <l:WindowInteractionMessageAction/>
        </l:InteractionMessageTrigger>        
    </i:Interaction.Triggers>

    <i:Interaction.Behaviors>
      <b:CloseButtonBehavior IsWindowCloseEnable="{Binding ElementName=CloseCheck, 
      Path=IsChecked}"/>
    </i:Interaction.Behaviors>

    <Grid  Background="#FFE6E6FA">            
        <Button Command="{Binding Path=GotoCommand63-2}" Content="Goto63-2" 
        FontSize="16" Foreground="White"  Width="470" Height="35" 
        Margin="40,0,503,266" Style="{StaticResource CommandTemplate}" 
        VerticalAlignment="Bottom" />
        <v:UserControlFooterN2 Margin="0,0,0,0" HorizontalAlignment="Left" 
        VerticalAlignment="Bottom" />
    </Grid>
</Window> 
 

Window2のViewModelであるViewModel2.csは下記のようになります。
ViewModelのコンストラクタでは、画面が呼び出された時は呼び出した親画面をHide(隠し)をします。
この時に、処理が重くて画面にちらつきが生じないないように、33,39,43行目の記述で、親画面のHideを遅らせています。
31,39,58行目を取って、40行目のコメントをはずすと、少々重い画面ですとちらつきが出ます。
開いている画面を閉じる時は48行目で親画面をShow(表示)します。


ViewModel
using System;

// ObservableCollection
using System.Collections.Generic;
using System.Linq;


//INotifyPropertyChanged
//PropertyChanged
using System.ComponentModel;

//参照設定が必要
//using System.Configuration;

using Livet;
using Livet.Commands;
using Livet.Messaging;
//CloseCommand
using Livet.Messaging.Windows;

//ICommand
using System.Windows.Input;

using System.Windows;
using Common;

namespace LivetWPFApplication100.ViewModels
{
    class ViewModel2 : ViewModel
    {
        Window win = null;
        public ViewModel2()
        {
            Loaded = new Livet.Commands.ListenerCommand<Window>((w) =>
            {
                if (NeedHideOwner && w.Owner != null && 
                w.Owner.Visibility == Visibility.Visible)
                {
                	win = w;
                    //w.Owner.Hide();
                }
            });

            Closing = new Livet.Commands.ListenerCommand<Window>((w) =>
            {
                if (NeedHideOwner && w.Owner != null)
                {
                    w.Owner.Show();
                }
            });
        }
        public bool NeedHideOwner { get; set; }
        public ICommand Loaded { get; private set; }
        public ICommand Closing { get; private set; }
        
        public void Initialize()
        {
            if (win != null) win.Owner.Hide();
            XCloseButtonManager.Disable();
        }