ViewModelはViewの知識を持ってはならないことを理解していますが、ViewModelでView(または直接MediaElement)への参照を持つ以外に、ViewModelからMediaElement.Play()メソッドを呼び出すにはどうすればよいですか?
その他の(リンクされた)質問:MVVMパターンに違反することなく、ViewModelからViewのコントロールの可視性を管理するにはどうすればよいですか?
3 に答える
Play()
1)ビューモデルから呼び出さないでください。代わりに(たとえばPlayRequested
)ビューモデルでイベントを発生させ、ビューでこのイベントをリッスンします。
ビューモデル:
public event EventHandler PlayRequested;
...
if (this.PlayRequested != null)
{
this.PlayRequested(this, EventArgs.Empty);
}
見る:
ViewModel vm = new ViewModel();
this.DataContext = vm;
vm.PlayRequested += (sender, e) =>
{
this.myMediaElement.Play();
};
Visibility
2)ビューモデルでパブリックブールプロパティを公開し、コントロールのプロパティをこのプロパティにバインドできます。Visibility
タイプVisibility
であり、ではないので、bool
コンバーターを使用する必要があります。
すべての後発者のために、
同じ結果を達成する方法はたくさんありますが、コードの保守が難しくない限り、実装方法によって異なりますが、特定の場合にMVVMパターンを壊しても問題ないと思います。
しかし、そうは言っても、パターン内でこれを行う方法は常にあると思います。他にどのような選択肢があるか知りたい場合に備えて、以下はその1つです。
タスク:
- ViewModelからUI要素、つまりMediaElementとView自体への直接参照は必要ありません。
- コマンドを使用してここで魔法を実行したい
ソリューション:
要するに、依存関係を解消するためにViewとViewModelの間にインターフェースを導入し、Viewはインターフェースを実装し、ViewModelがインターフェースとのみ通信するようにしながら、MediaElementの直接制御を担当します。必要に応じて、テスト目的で他の実装と交換できます。長いバージョンがあります。
以下のようにIMediaServiceと呼ばれるインターフェースを導入します。
public interface IMediaService { void Play(); void Pause(); void Stop(); void Rewind(); void FastForward(); }
ビューにIMediaServiceを実装します。
public partial class DemoView : UserControl, IMediaService { public DemoView() { InitializeComponent(); } void IMediaService.FastForward() { this.MediaPlayer.Position += TimeSpan.FromSeconds(10); } void IMediaService.Pause() { this.MediaPlayer.Pause(); } void IMediaService.Play() { this.MediaPlayer.Play(); } void IMediaService.Rewind() { this.MediaPlayer.Position -= TimeSpan.FromSeconds(10); } void IMediaService.Stop() { this.MediaPlayer.Stop(); } }
次に、DemoView.XAMLでいくつかのことを行います。
- MediaElementに名前を付けて、背後のコードが上記のようにアクセスできるようにします。
<MediaElement Source="{Binding CurrentMedia}" x:Name="MediaPlayer"/>
- ビューに名前を付けて、パラメーターとして渡すことができるようにします。
- 後で使用するために対話型名前空間をインポートします(簡単にするために、一部のデフォルトの名前空間は省略されています)。
<UserControl x:Class="Test.DemoView" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ia="http://schemas.microsoft.com/expression/2010/interactivity" x:Name="MediaService">
- トリガーを介してLoadedイベントをフックアップし、コマンドを介してビュー自体をビューモデルに渡します
<ia:Interaction.Triggers> <ia:EventTrigger EventName="Loaded"> <ia:InvokeCommandAction Command="{Binding LoadedCommand}" CommandParameter="{Binding ElementName=MediaService}"></ia:InvokeCommandAction> </ia:EventTrigger> </ia:Interaction.Triggers>
- 最後になりましたが、コマンドを使用してメディアコントロールを接続する必要があります。
<Button Command="{Binding PlayCommand}" Content="Play"></Button> <Button Command="{Binding PauseCommand}" Content="Pause"></Button> <Button Command="{Binding StopCommand}" Content="Stop"></Button> <Button Command="{Binding RewindCommand}" Content="Rewind"></Button> <Button Command="{Binding FastForwardCommand}" Content="FastForward"></Button>
これで、ViewModel内のすべてをキャッチできます(ここではprismのDelegateCommandを使用しています)。
public class AboutUsViewModel : SkinTalkViewModelBase, IConfirmNavigationRequest { public IMediaService {get; private set;} private DelegateCommand<IMediaService> loadedCommand; public DelegateCommand<IMediaService> LoadedCommand { get { if (this.loadedCommand == null) { this.loadedCommand = new DelegateCommand<IMediaService>((mediaService) => { this.MediaService = mediaService; }); } return loadedCommand; } } private DelegateCommand playCommand; public DelegateCommand PlayCommand { get { if (this.playCommand == null) { this.playCommand = new DelegateCommand(() => { this.MediaService.Play(); }); } return playCommand; } } . . // other commands are not listed, but you get the idea . }
補足:Prismの自動配線機能を使用してViewとViewModelをリンクしています。したがって、ビューのコードビハインドファイルにはDataContext割り当てコードがなく、そのままにしておくことを好みます。したがって、この結果を達成するために、純粋にコマンドを使用することにしました。
アプリケーションでイベントが発生するたびに、メディア要素を使用してUIでサウンドを再生します。これを処理するビューモデルは、タイプUriのSourceプロパティを使用して作成されました(notifyプロパティが変更されていますが、UIに通知するためにそれが必要であることはすでにわかっています)。
ソースが変更されるたびに(そしてこれはあなた次第です)、ソースプロパティをnullに設定するだけです(これが、ソースプロパティが文字列ではなくUriである必要がある理由です。MediaElementは当然例外をスローします。NotSupportedExceptionだと思います)。必要なURIに設定します。
おそらく、このヒントの最も重要な側面は、MediaElementのプロパティLoadedBehaviourをビューのXAMLで再生するように設定する必要があることです。うまくいけば、達成したいことのために背後にあるコードは必要ありません。
トリックは非常に単純なので、完全な例を投稿することはしません。ビューモデルの再生関数は次のようになります。
private void PlaySomething(string fileUri)
{
if (string.IsNullOrWhiteSpace(fileUri))
return;
// HACK for MediaElement: to force it to play a new source, set source to null then put the real source URI.
this.Source = null;
this.Source = new Uri(fileUri);
}
Sourceプロパティは次のとおりですが、特別なことは何もありません。
#region Source property
/// <summary>
/// Stores Source value.
/// </summary>
private Uri _Source = null;
/// <summary>
/// Gets or sets file URI to play.
/// </summary>
public Uri Source
{
get { return this._Source; }
private set
{
if (this._Source != value)
{
this._Source = value;
this.RaisePropertyChanged("Source");
}
}
}
#endregion Source property
可視性、およびこのようなものに関しては、コンバーター(たとえば、ブール値から可視性まで、CodePlex for WPF、SL、WP7,8にあります)を使用して、コントロールのプロパティをビューモデルのプロパティ(IsVisibleなど)にバインドできます。 。このようにして、ビューのアスペクトの一部を制御します。または、ビューモデルでVisibilityプロパティにSystem.Windows.Visibilityと入力することもできます(ここではパターン違反は見られません)。本当に、それはそれほど珍しいことではありません。
幸運を、
アンドレイ
PS .NET 4.5は私がこれをテストしたバージョンであることに言及する必要がありますが、他のバージョンでも機能するはずです。