Twitterクライアントの例を段階的に使用したModel-View-ViewModelデザインパターンを使用した開発

はじめに


この記事は、MVVMとWPFの操作に特化しています。 Twitterクライアントの開発プロセスについて説明します。 開発プロセスはステップに分けられます。 各ステップの最後に、並行してアプリケーションを作成するリーダーには、動作するアプリケーションが必要です。 後続の各ステップは、前のステップで作成された機能にいくつかの機能を追加します。 サードパーティのライブラリTweetSharpを使用しました 。 私が英語で書いた元の記事と同様に、ソースコードへのリンクはここにあります

この記事は、WPF開発の初心者を対象としています。 ただし、読者はWPF、特にマスタリングされたデータバインディングの最初の経験を持っていると想定されています。

MVVMを使用する必要がある理由を説明しません。これは、Josh Smithの記事「モデルビュープレゼンテーションモデルを使用したWPFアプリケーション」によく書かれていると思います。 この記事を読みたくない場合-信じてください-WPFの場合のGUIの誤った設計は大きな頭痛の種になります。





顧客のツイッター予備分析



クライアントの設計の設計に移る前に、既存のクライアントのGUIを見て、ユーザーに提供する機能を理解しましょう。

画像

ユーザーは、ページの上部でいくつかのタブを使用します-最近、返信、ユーザーなど。 選択したブックマークに応じて、メインウィンドウのスペースに異なる情報が表示されます。 たとえば、[最近]をクリックすると、サブスクリプションの最新のツイートが表示され、メインウィンドウで[メッセージ]をクリックすると、ダイレクトメッセージが表示されます。

もう少し考えて、TabControlを使用するのが妥当であることを理解します。



MVVMについて簡単に


例から始めましょう。 ツイートを表示するUserControlを記述する必要があるとします。 [ユーザーコントロールの追加]をクリックし、必要なコントロールをxamlにスローします。 次に、コードビハインドでツイートを保存するプロパティを作成します。 次に、同じコードビハインドでイベントハンドラーを追加します。たとえば、ツイートのUserPictureをクリックします。 一般に、すべては、WinFormsなどの前世代のテクノロジーを使用してこの制御を行っている場合と同じです。 このコントロールを詳しく見てみましょう-ユーザーに見える部分の説明があります-それらのxaml。 また、背後にあるコードもあります。

ここで、最初の機能要求を取得したとします-ユーザーは他のツイートを表示したいが、まったく同じコントロールでした。 コードビハインドで適切なフラグを立てることができます。または、ソフトウェア設計がより高度な場合は、GoFの戦略を使用します。 しばらくすると、2番目の機能要求が届きます-各ツイートにはユーザーにとって不必要な情報が多すぎて、コントロールを新しく見直す必要がありますが、同時に古いユーザーを怒らせ、古い視覚表現で作業する機会を残したくありません。 など-要件は新しくなりつつあり、さまざまなトリックと独創的なソリューションを使用してそれらを実装します。

やめて! 時間をさかのぼってみましょう-コントロールが1つしかない状況に戻りましょう。 コードビハインドとコントロールクラス自体を広めるとどうなりますか? それらは本質を2つに分けます。 ビューと呼ばれるもの-これはユーザーに表示されるもので、UserControlから継承されたクラスです。 2番目の名前はViewModelです。これらは、Viewを通じてユーザーに表示されるものに直接関連するデータであり、アバターをクリックするなどのイベントハンドラー関数です。 その後、最初の機能要求を簡単に解決できます。ViewModelを記述し、古いビューをそれにマップするだけです。 最初の機能リクエストも簡単に解決できます。異なるビューを1つのViewModelにマップできます。 いい考えだと思います。

しかし、ViewとViewModelを接続する方法は? このために、データバインディング-WPFの主要ツールが使用されます。 使用するDataContextを何らかの方法でViewに伝え、データをどこから取得するかを伝えれば、コードビハインドの場合とほぼ同じようにデータにバインドします。 ここでは、技術的な詳細を意図的に省略します-詳細は後で説明します。

これは、MVVMを使用して記述されたアプリケーションの典型的なクラス図です。

画像

Viewクラスがあり、ViewModelのデータを使用しますが、モデルについては何も知りません(この場合、モデルはTweetSharpライブラリに実装されます)。 一方、ViewModelは、どのViewがそれらを使用するかを知りませんが、Modelから情報を取得できます。

通常、残りのViewとViewModelを集約するメインのViewとViewModelもあります。



ステップ1.アプリケーションフレームワーク


最初に、1つのTabItem-Recentのみを含むTabControlでアプリケーションを作成します。 MVVMに従って行動します。

新しいWPFアプリケーションを作成しましょう。 MainWindowウィンドウで、TabControlを追加します。



<TabControl Height="400"

HorizontalAlignment="Left"

Name="Tabs"

VerticalAlignment="Top" Width="300">

/>









このウィンドウは、ビューのメイン要素になります。 他のすべての要素(tabItems)が表示されます。 対応するViewModelクラスに対応する必要があります。これは、そのフィールドのメインになります。 MainWindowのコードビハインドではなく、その中にデータを保存し、イベントハンドラー関数を記述します。 新しいパブリッククラスSimpleTwitterClientViewModelを作成します。 とりあえず空にします。 次に、コードクラスではなくSimpleTwitterClientViewModelでデータを検索するようにウィンドウクラスに指示する必要があります。 DataContextを設定する必要があるもの。 これを行うには、ViewModelフィールドをコードビハインドに追加し、nameプロパティをウィンドウに追加して、DataContextを定義します。

<Window x:Class="SimpleTwitterClient.MainWindow"

x:Name="MainWindowInstance"

xmlns:view="clr-namespace:SimpleTwitterClient.View"

xmlns:viewModel="clr-namespace:SimpleTwitterClient.ViewModel"

DataContext="{BindingViewModel,ElementName=MainWindowInstance}">








public partial class MainWindow : Window

{

private SimpleTwitterClientViewModel _viewModel;



public SimpleTwitterClientViewModel ViewModel

{

get { return _viewModel; }

set { _viewModel = value; }

}



public MainWindow()

{

_viewModel = new SimpleTwitterClientViewModel();

InitializeComponent();

}

}






次に、RecentのViewとViewModelを作成します。 RecentView-空のコードビハインドを持つUserControlのみになります。 コントロールを追加して、現在の空のTabItemと区別します。 RecentViewModelを空にします。

SimpleTwitterClientViewModelは、TabItemのViewModelを格納するクラスになります。 これまでのところ、最近のみがあります。 それでは、このコードを書きましょう。

public class SimpleTwitterClientViewModel

{

RecentViewModel _recentPage = new RecentViewModel();



public RecentViewModel RecentPage

{

get { return _recentPage; }

set { _recentPage = value; }

}

}






ここでアプリケーションを起動すると、これまでのところ何も変わっていないことがわかります。 また、TabItemにRecentViewを表示するように指示する必要があります。 どうやってやるの? これは微妙ですが重要なポイントです-最初に、ContentをRecentPageに設定する必要があります。

/>





次に、ViewModelからViewへのビューを作成する必要があります。 TabItemのコンテンツがRecentViewModelの場合、RecentViewを表示する必要があることを確認するコード。 このコードは次のとおりです。

<Window.Resources>

<DataTemplateDataType="{x:TypeviewModel:RecentViewModel}">

<view:RecentView />







</Window.Resources>

これらすべてのアクションの結果として、次のようなアプリケーションがあります。

画像

なぜそのような困難なのですか? これはほんの始まりに過ぎず、アプリケーションがより複雑になると、MVVMの利点を理解できます。



ステップ2.最初のツイート


RecentPageにデータ、最新のツイートを入力します。 このために、TweetSharpライブラリーを使用します。 アセンブリのリストに追加します。

Twitter APIでアプリケーションを使用するには、一連の詐欺を行う必要があります。

1. ConsumerKeyおよびConsumerSecret。 リンクhttp://dev.twitter.com/に従って、「登録」をクリックします。 その後、アプリケーションに関する情報を含むページが表示され、そこからConsumerKeyとConsumerSecretを取得して、設定に保存します。

2. PIN(oauth_verifier)。 アプリケーションを初めて起動すると、ConsumerSecretとConsumerKeyを含むリクエストをサーバーに送信する必要があります。

FluentTwitter.SetClientInfo(

new TwitterClientInfo

{

ConsumerKey = Settings.Default.ConsumerKey,

ConsumerSecret = Settings.Default.ConsumerSecret

});



var twit = FluentTwitter.CreateRequest().Authentication.GetRequestToken();



var response = twit.Request();



var RequestToken = response.AsToken();

twit = twit.Authentication.AuthorizeDesktop(RequestToken.Token);






最後の行はデフォルトでブラウザを開きます。 アプリケーションによるサービスの使用を許可するかどうかについて質問があります。[許可]をクリックし、アプリケーションのダイアログボックスにPINを入力します。 getPinFromUserメソッドでこのダイアログを呼び出します。

string verifier = getPinFromUser();





3. AccesToken。 次に、consumerKey、consumerSecret、Pinを含むリクエストを送信します。 サービスは、将来使用されるAccessTokenを返します。

twit.Authentication.GetAccessToken(RequestToken.Token, verifier);

var response2 = twit.Request();






4. AccessTokenを更新します。 しばらくすると、サービスはAccessTokenの更新を要求する場合があります。



OAuthHandlerクラスをModelフォルダーに追加します。 そこにUserControl AskPinFromUserDialogを追加します。 そして、アプリケーションのリソースに、アプリケーションを登録したときにTwitterから受け取ったConsumerKey、ConsumerSecretを追加します。 OauthInfoFileNameを追加して、アプリケーションの構成ファイルへのパスを書き込みます。 ツイッターから受け取ったキーはそこに保存されます。 非常に安全ではありませんが、簡単です。 最後に、このクラスのオブジェクトをメインのViewModelに追加します。

Model.OAuthHandler _oauthHandler = newModel.OAuthHandler();





これでRecentPageを使い始めることができます。 開始するには、ツイートストレージコンテナをRecentPageクラスに追加します。



public ObservableCollection Tweets

{

get; set;

}



ObservableCollection, - List View, .

. - _oauthHandler ViewModel. RecentViewModel, _oauthHandler .

, _oauthHandler , .

. LoadTweets RecentViewModel:

public void LoadTweets()

{

TwitterResult response = FluentTwitter

.CreateRequest()

.AuthenticateWith(

Settings.Default.ConsumerKey,

Settings.Default.ConsumerSecret,

Model.OAuthHandler.Token,

Model.OAuthHandler.TokenSecret)

.Statuses()

.OnHomeTimeline().AsJson().Request());



var statuses = response.AsStatuses();

foreach (TwitterStatus status in statuses)

{

Tweets.Add(status);

}

}







RecentViewModel:

public RecentViewModel(Model.OAuthHandleroauthHandler)

{

_oauthHandler = oauthHandler;

Tweets = newObservableCollection();

LoadTweets();

}



ListBox :

<ListBoxx:Name="RecentTweetList"

ItemsSource="{Binding Path=Tweets}"

IsSynchronizedWithCurrentItem="True"/>







, TweetStatus.ToString(). , ListBox , TweetStatus. , , DataTemplate UserControl.Resources. DataTemplate , TweetStatus. , TweetStatus Text, Binding Path=Text.

DataTemplate:



<DataTemplate x:Key="TweetItemTemplate">

<Grid x:Name="TTGrid">

<Grid.RowDefinitions>

/>

/>

</Grid.RowDefinitions>

<Grid.ColumnDefinitions>

/>

/>

/>

</Grid.ColumnDefinitions>

<Image Source="{Binding Path=User.ProfileImageUrl}"

Name="UserAvatarImage" />

<TextBlock

Name="ScreenNameTextBlock"

Text="{Binding Path=User.ScreenName}"

Grid.Row="1"Grid.ColumnSpan="2"/>

<TextBlock

Text="{Binding Path=Text}"

TextWrapping="Wrap"Grid.Column="1"Grid.ColumnSpan="2" />



















次に、宣言されたDataTemplateを使用するようListBoxに指示します。

ItemTemplate="{StaticResourceTweetItemTemplate}"





アプリケーションは次のようになります。

画像



ステップ3.新しいページ


[最近]タブには、フォローしているユーザーが最後に投稿したツイートと、自分のツイートが表示されます。 TweetSharpに関しては、これはHomeTimelineです。 しかし、これらはあなたの興味を引くかもしれないすべてのツイートではありません。 OnHomeTimelineメソッドが宣言されているTwitterStatusesExtensionsクラスにアクセスすると、OnFriendsTimeline、OnListTimeline、ByByMeなどのさまざまな基準でグループ化されたツイートのリストを返す他のメソッドが表示されます。

フォローしているユーザーのリツイートを含むページを作成します。 Recentの作成と同様に、RetweetsViewModelを作成します。 コードを詳細に説明することはせず、読者に演習を残します。 ビューも書くと思いますか? うーん、最近とリツイートに表示される情報は構造的に同じです。 MVVMとWPFはコピーアンドペーストを最小限に抑えます-この場合、RetweetsViewについてはまだ考えていません。 ステップ3の最後に、これが不要であることがわかります。

次に、RetweetsViewModelクラスのオブジェクトをメインのViewModelに追加する必要があります。 ページが2つしかない場合、これは通常の解決策です。 しかし、私たちは多くのページを作りたいです。 したがって、このソリューションは最適ではありません。 メインViewModelにさまざまなViewModelを持つコンテナを保存することをお勧めします。 RetweetsViewModelはRecentViewModelと非常によく似ており、クラスからExtract Interfaceを簡単に使用できることがわかります。 また、OauthHandlerをプロパティにし、コンストラクターで初期化しませんが、基本クラスで初期化します。 IPageBaseインターフェイスを呼び出します。



public interface IPageBase

{

void LoadTweets();

ObservableCollection Tweets { get; set; }

}





ViewModel:

ObservableCollection _pages;



public ObservableCollection Pages

{

get { return _pages; }

set { _pages = value; }

}



public SimpleTwitterClientViewModel()

{

_pages = new ObservableCollection();

_pages.Add(new RecentViewModel());

_pages.Add(new RetweetsViewModel());

foreach (var page in _pages)

{

page.LoadTweets();

}

}



View DataTemplate RetweetsPage. TabItem – tabItem ViewModel RecentPage:

/>






TabItems Pages. TabControl ItemsSource. Pages TabItems – Pages. – TabItems Header. header TabItems? Name IPageBase. -. , TabItem Header:



<TabControlName="Tabs"

ItemsSource="{Binding Pages}">

<TabControl.ItemContainerStyle>



</TabControl.ItemContainerStyle>









– RetweetsViewModel View. RetweetsView. RecentView! MVVM!

<DataTemplateDataType="{x:TypeviewModel:RetweetsViewModel}">

<view:RecentView />









. :

画像

DataTemplate – ViewModel .

– follower & following.



4. ICommand

, . MVVM – OnMouseClick. . , . TextBox , :

画像

code behind – EnterTweetTextBox.Text. MVVM . ViewModel , Send. , .

, EnterTweetTextBox . SimpleTwitterClientViewModel – Message. Text:

<TextBoxName="EnterTweetTextBox"

Text="{BindingMessage}"/>







SendTweet SimpleTwitterClientViewModel:

private void SendTweet()

{

var twitter = FluentTwitter.CreateRequest();

twitter.AuthenticateWith(

Settings.Default.ConsumerKey,

Settings.Default.ConsumerSecret,

OAuthHandler.Token,

OAuthHandler.TokenSecret);

twitter.Statuses().Update(Message);



var response = twitter.Request();

//you can verify the response here

}







– ? – Button Command, . ICommand. SendMessage, , . ? – -adapter ICommand. Josh Smith:

internal class RelayCommand : ICommand

{

#region Fields



readonly Action _execute;

readonly Func _canExecute;



#endregion



#region Constructors



public RelayCommand(Action execute)

: this(execute, null)

{

}



public RelayCommand(Action execute, Func canExecute)

{

if (execute == null)

throw new ArgumentNullException("execute");



_execute = execute;

_canExecute = canExecute;

}



#endregion // Constructors



#region ICommand Members



[DebuggerStepThrough]

public bool CanExecute(object parameter)

{

return _canExecute == null ? true : _canExecute();

}



public event EventHandler CanExecuteChanged

{

add

{

if (_canExecute != null)

CommandManager.RequerySuggested += value;

}

remove

{

if (_canExecute != null)

CommandManager.RequerySuggested -= value;

}

}



public void Execute(object parameter)

{

_execute();

}



#endregion // ICommand Members

}



RelayCoommand ICommand. , SendTweet ICommand:

RelayCommand _sendCommand;

public ICommand SendCommand

{

get

{

if (_sendCommand == null)

{

_sendCommand = new RelayCommand(() => this.SendTweet());

}

return _sendCommand;

}

}







, SendButton , SendCommand:

<Button Name="SendTweetButton"

Command="{Binding SendCommand}"/>






, ? :

画像

, – .

, feature: userpicture, TabItem . TabItem , .















public interface IPageBase

{

void LoadTweets();

ObservableCollection Tweets { get; set; }

}





ViewModel:

ObservableCollection _pages;



public ObservableCollection Pages

{

get { return _pages; }

set { _pages = value; }

}



public SimpleTwitterClientViewModel()

{

_pages = new ObservableCollection();

_pages.Add(new RecentViewModel());

_pages.Add(new RetweetsViewModel());

foreach (var page in _pages)

{

page.LoadTweets();

}

}



View DataTemplate RetweetsPage. TabItem – tabItem ViewModel RecentPage:

/>






TabItems Pages. TabControl ItemsSource. Pages TabItems – Pages. – TabItems Header. header TabItems? Name IPageBase. -. , TabItem Header:



<TabControlName="Tabs"

ItemsSource="{Binding Pages}">

<TabControl.ItemContainerStyle>



</TabControl.ItemContainerStyle>









– RetweetsViewModel View. RetweetsView. RecentView! MVVM!

<DataTemplateDataType="{x:TypeviewModel:RetweetsViewModel}">

<view:RecentView />









. :

画像

DataTemplate – ViewModel .

– follower & following.



4. ICommand

, . MVVM – OnMouseClick. . , . TextBox , :

画像

code behind – EnterTweetTextBox.Text. MVVM . ViewModel , Send. , .

, EnterTweetTextBox . SimpleTwitterClientViewModel – Message. Text:

<TextBoxName="EnterTweetTextBox"

Text="{BindingMessage}"/>







SendTweet SimpleTwitterClientViewModel:

private void SendTweet()

{

var twitter = FluentTwitter.CreateRequest();

twitter.AuthenticateWith(

Settings.Default.ConsumerKey,

Settings.Default.ConsumerSecret,

OAuthHandler.Token,

OAuthHandler.TokenSecret);

twitter.Statuses().Update(Message);



var response = twitter.Request();

//you can verify the response here

}







– ? – Button Command, . ICommand. SendMessage, , . ? – -adapter ICommand. Josh Smith:

internal class RelayCommand : ICommand

{

#region Fields



readonly Action _execute;

readonly Func _canExecute;



#endregion



#region Constructors



public RelayCommand(Action execute)

: this(execute, null)

{

}



public RelayCommand(Action execute, Func canExecute)

{

if (execute == null)

throw new ArgumentNullException("execute");



_execute = execute;

_canExecute = canExecute;

}



#endregion // Constructors



#region ICommand Members



[DebuggerStepThrough]

public bool CanExecute(object parameter)

{

return _canExecute == null ? true : _canExecute();

}



public event EventHandler CanExecuteChanged

{

add

{

if (_canExecute != null)

CommandManager.RequerySuggested += value;

}

remove

{

if (_canExecute != null)

CommandManager.RequerySuggested -= value;

}

}



public void Execute(object parameter)

{

_execute();

}



#endregion // ICommand Members

}



RelayCoommand ICommand. , SendTweet ICommand:

RelayCommand _sendCommand;

public ICommand SendCommand

{

get

{

if (_sendCommand == null)

{

_sendCommand = new RelayCommand(() => this.SendTweet());

}

return _sendCommand;

}

}







, SendButton , SendCommand:

<Button Name="SendTweetButton"

Command="{Binding SendCommand}"/>






, ? :

画像

, – .

, feature: userpicture, TabItem . TabItem , .















public interface IPageBase

{

void LoadTweets();

ObservableCollection Tweets { get; set; }

}





ViewModel:

ObservableCollection _pages;



public ObservableCollection Pages

{

get { return _pages; }

set { _pages = value; }

}



public SimpleTwitterClientViewModel()

{

_pages = new ObservableCollection();

_pages.Add(new RecentViewModel());

_pages.Add(new RetweetsViewModel());

foreach (var page in _pages)

{

page.LoadTweets();

}

}



View DataTemplate RetweetsPage. TabItem – tabItem ViewModel RecentPage:

/>






TabItems Pages. TabControl ItemsSource. Pages TabItems – Pages. – TabItems Header. header TabItems? Name IPageBase. -. , TabItem Header:



<TabControlName="Tabs"

ItemsSource="{Binding Pages}">

<TabControl.ItemContainerStyle>



</TabControl.ItemContainerStyle>









– RetweetsViewModel View. RetweetsView. RecentView! MVVM!

<DataTemplateDataType="{x:TypeviewModel:RetweetsViewModel}">

<view:RecentView />









. :

画像

DataTemplate – ViewModel .

– follower & following.



4. ICommand

, . MVVM – OnMouseClick. . , . TextBox , :

画像

code behind – EnterTweetTextBox.Text. MVVM . ViewModel , Send. , .

, EnterTweetTextBox . SimpleTwitterClientViewModel – Message. Text:

<TextBoxName="EnterTweetTextBox"

Text="{BindingMessage}"/>







SendTweet SimpleTwitterClientViewModel:

private void SendTweet()

{

var twitter = FluentTwitter.CreateRequest();

twitter.AuthenticateWith(

Settings.Default.ConsumerKey,

Settings.Default.ConsumerSecret,

OAuthHandler.Token,

OAuthHandler.TokenSecret);

twitter.Statuses().Update(Message);



var response = twitter.Request();

//you can verify the response here

}







– ? – Button Command, . ICommand. SendMessage, , . ? – -adapter ICommand. Josh Smith:

internal class RelayCommand : ICommand

{

#region Fields



readonly Action _execute;

readonly Func _canExecute;



#endregion



#region Constructors



public RelayCommand(Action execute)

: this(execute, null)

{

}



public RelayCommand(Action execute, Func canExecute)

{

if (execute == null)

throw new ArgumentNullException("execute");



_execute = execute;

_canExecute = canExecute;

}



#endregion // Constructors



#region ICommand Members



[DebuggerStepThrough]

public bool CanExecute(object parameter)

{

return _canExecute == null ? true : _canExecute();

}



public event EventHandler CanExecuteChanged

{

add

{

if (_canExecute != null)

CommandManager.RequerySuggested += value;

}

remove

{

if (_canExecute != null)

CommandManager.RequerySuggested -= value;

}

}



public void Execute(object parameter)

{

_execute();

}



#endregion // ICommand Members

}



RelayCoommand ICommand. , SendTweet ICommand:

RelayCommand _sendCommand;

public ICommand SendCommand

{

get

{

if (_sendCommand == null)

{

_sendCommand = new RelayCommand(() => this.SendTweet());

}

return _sendCommand;

}

}







, SendButton , SendCommand:

<Button Name="SendTweetButton"

Command="{Binding SendCommand}"/>






, ? :

画像

, – .

, feature: userpicture, TabItem . TabItem , .
















All Articles