2010-06-15 4 views
9

Ich bin neu bei WPF und MVVM. Ich denke, das ist eine einfache Frage. Mein ViewModel führt einen asynchronen Aufruf durch, um Daten für ein DataGrid zu erhalten, das an eine ObservableCollection im ViewModel gebunden ist. Wenn die Daten geladen werden, setze ich die richtige ViewModel-Eigenschaft und das DataGrid zeigt die Daten ohne Probleme an. Ich möchte jedoch einen visuellen Hinweis für den Benutzer einführen, dass die Daten geladen werden. Also, mit Blend, fügte ich das meinem Markup:Wie kann ich den VisualState in einer View vom ViewModel ändern?

 <VisualStateManager.VisualStateGroups> 
     <VisualStateGroup x:Name="LoadingStateGroup"> 
      <VisualState x:Name="HistoryLoading"> 
       <Storyboard> 
        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="HistoryGrid"> 
         <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/> 
        </ObjectAnimationUsingKeyFrames> 
       </Storyboard> 
      </VisualState> 
      <VisualState x:Name="HistoryLoaded"> 
       <Storyboard> 
        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="WorkingStackPanel"> 
         <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Hidden}"/> 
        </ObjectAnimationUsingKeyFrames> 
       </Storyboard> 
      </VisualState> 
     </VisualStateGroup> 
    </VisualStateManager.VisualStateGroups> 

Ich denke Ich weiß, wie man den Zustand in meinem Code-Behind (etwas ähnlich wie diese) zu ändern:

VisualStateManager.GoToElementState(LayoutRoot, "HistoryLoaded", true); 

jedoch Der Ort, an dem ich dies tun möchte, ist in der I/O-Komplettierungsmethode meines ViewModel, die keinen Verweis auf die entsprechende View hat. Wie würde ich dies mit dem MVVM-Muster erreichen?

Antwort

7

Sie etwas tun können:

XAML

<Window x:Class="WpfSOTest.BusyWindow" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="clr-namespace:WpfSOTest" 
    Title="BusyWindow" 
    Height="300" 
    Width="300"> 
<Window.Resources> 
    <local:VisibilityConverter x:Key="VisibilityConverter" /> 
</Window.Resources> 
<Grid> 
    <Grid.RowDefinitions> 
     <RowDefinition /> 
     <RowDefinition Height="Auto" /> 
    </Grid.RowDefinitions> 
    <Border Grid.Row="0"> 
     <Grid> 
      <Border> 
       <Rectangle Width="400" 
          Height="400" 
          Fill="#EEE" /> 
      </Border> 
      <Border Visibility="{Binding IsBusy, Converter={StaticResource VisibilityConverter}}"> 
       <Grid> 
        <Rectangle Width="400" 
           Height="400" 
           Fill="#AAA" /> 
        <TextBlock Text="Busy" 
           HorizontalAlignment="Center" 
           VerticalAlignment="Center" /> 
       </Grid> 
      </Border> 
     </Grid> 
    </Border> 
    <Border Grid.Row="1"> 
     <Button Click="ChangeVisualState">Change Visual State</Button> 
    </Border> 
</Grid> 

Code:

public partial class BusyWindow : Window 
{ 
    ViewModel viewModel = new ViewModel(); 

    public BusyWindow() 
    { 
     InitializeComponent(); 

     DataContext = viewModel; 
    } 

    private void ChangeVisualState(object sender, RoutedEventArgs e) 
    { 
     viewModel.IsBusy = !viewModel.IsBusy; 
    } 
} 

public class ViewModel : INotifyPropertyChanged 
{ 
    protected Boolean _isBusy; 
    public Boolean IsBusy 
    { 
     get { return _isBusy; } 
     set { _isBusy = value; RaisePropertyChanged("IsBusy"); } 
    } 

    public ViewModel() 
    { 
     IsBusy = false; 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
    public void RaisePropertyChanged(String propertyName) 
    { 
     PropertyChangedEventHandler temp = PropertyChanged; 
     if (temp != null) 
     { 
      temp(this, new PropertyChangedEventArgs(propertyName)); 
     } 
    } 
} 

class VisibilityConverter : IValueConverter 
{ 
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
     switch (((Boolean)value)) 
     { 
      case true: 
       return Visibility.Visible; 
     } 

     return Visibility.Collapsed; 
    } 

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
    { 
     throw new NotImplementedException(); 
    } 
} 

----------------------- Aktualisiert Code ----- ------------------

XAML

<Grid> 
    <Grid.RowDefinitions> 
     <RowDefinition /> 
     <RowDefinition Height="Auto" /> 
    </Grid.RowDefinitions> 
    <Border Grid.Row="0"> 
     <Grid> 
      <local:MyBorder IsBusy="{Binding IsBusy}"> 
       <Grid> 
        <TextBlock Text="{Binding IsBusy}" 
           HorizontalAlignment="Center" 
           VerticalAlignment="Center" /> 
       </Grid> 
      </local:MyBorder> 
     </Grid> 
    </Border> 
    <Border Grid.Row="1"> 
     <Button Click="ChangeVisualState">Change Visual State</Button> 
    </Border> 
</Grid> 

Vorlage

<Style TargetType="{x:Type local:MyBorder}"> 
    <Setter Property="Template"> 
     <Setter.Value> 
      <ControlTemplate TargetType="{x:Type local:MyBorder}"> 
       <Border Name="RootBorder"> 
        <Border.Background> 
         <SolidColorBrush x:Name="NormalBrush" 
             Color="Transparent" /> 
        </Border.Background> 
        <VisualStateManager.VisualStateGroups> 
         <VisualStateGroup Name="CommonGroups"> 
          <VisualState Name="Normal" /> 
         </VisualStateGroup> 
         <VisualStateGroup Name="CustomGroups"> 
          <VisualState Name="Busy"> 
           <VisualState.Storyboard> 
            <Storyboard> 
             <ColorAnimation Storyboard.TargetName="NormalBrush" 
                 Storyboard.TargetProperty="Color" 
                 Duration="0:0:0.5" 
                 AutoReverse="True" 
                 RepeatBehavior="Forever" 
                 To="#EEE" /> 
            </Storyboard> 
           </VisualState.Storyboard> 
          </VisualState> 
         </VisualStateGroup> 
        </VisualStateManager.VisualStateGroups> 
        <ContentPresenter /> 
       </Border> 
      </ControlTemplate> 
     </Setter.Value> 
    </Setter> 
</Style> 

Benutzerdefinierte Element

[TemplateVisualState(GroupName = "CustomGroups", Name = "Busy")] 
public class MyBorder : ContentControl 
{ 
    static MyBorder() 
    { 
     DefaultStyleKeyProperty.OverrideMetadata(typeof(MyBorder), new FrameworkPropertyMetadata(typeof(MyBorder))); 
    } 

    public Boolean IsBusy 
    { 
     get { return (Boolean)GetValue(IsBusyProperty); } 
     set { SetValue(IsBusyProperty, value); } 
    } 

    public static readonly DependencyProperty IsBusyProperty = 
     DependencyProperty.Register("IsBusy", typeof(Boolean), typeof(MyBorder), new UIPropertyMetadata(IsBusyPropertyChangedCallback)); 

    static void IsBusyPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     (d as MyBorder).OnIsBusyPropertyChanged(d, e); 
    } 

    private void OnIsBusyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     if (Convert.ToBoolean(e.NewValue)) 
     { 
      VisualStateManager.GoToState(this, "Busy", true); 
     } 
     else 
     { 
      VisualStateManager.GoToState(this, "Normal", true); 
     } 
    } 
} 
+0

Dies ist eine interessante Technik. Vielen Dank. Ich kann diesen Weg gehen - aber ich möchte zuerst sehen, ob ich den VisualState der Ansicht basierend auf einer Änderung einer ViewModel-Eigenschaft irgendwie ändern kann. Ich denke, das ist der einzige Weg, wie ich Animationen machen kann - wie Ein- und Ausblenden basierend auf dem Zustand. –

+0

Ich habe die Sichtbarkeitseigenschaft hier verwendet, aber Sie können in Ihrem benutzerdefinierten Steuerelement eine Abhängigkeitseigenschaft vom Typ boolean erstellen, die IsBusy-Eigenschaft an diese Eigenschaft binden und den visuellen Status im Steuerelement selbst ändern, wenn sich die Eigenschaft in a ändert spezifischer Wert. – decyclone

+0

Diese Antwort ist awesommme !!! –

2

Was ich in der Vergangenheit getan habe, ist ein Ereignis in meiner VM zu deklarieren, zu dem die Ansicht abonniert. Wenn ich dann anzeigen möchte, dass die Besetztzeichenanzeige nicht mehr angezeigt wird, rufe ich das Ereignis in der VM an.

+3

Eine Eigenschaft ist besser als ein Ereignis für diese. –

12

Der Standard Weg dies zu tun ist normalerweise eine Eigenschaft in Ihrem Viewmodel (entweder Abhängigkeitseigenschaft oder eine Beteiligung an INotifyPropertyChanged), die bedeuten würde, dass Daten geladen werden - vielleicht bool IsLoadingData oder ähnlich. Sie setzen es auf "true", wenn Sie mit dem Laden beginnen, und setzen es auf "false", wenn Sie fertig sind.

Dann würden Sie einen Trigger oder visuellen Status an diese Eigenschaft in der Ansicht binden und die Ansicht verwenden, um zu beschreiben, wie dem Benutzer angezeigt wird, dass die Daten geladen werden.

Dieser Ansatz hält die Trennung in dem das Viewmodel ist die logische Darstellung der Sicht des Benutzers, und muss nicht in der aktuellen Anzeige teilnehmen - oder Kenntnis von Animationen, visueller Zuständen etc.

nutzen zu können, ein DataStateBehavior visuelle Zustände auf eine Bindung in Silverlight basiert zu ändern:

<TheThingYouWantToModify ...> 
<i:Interaction.Behaviors> 
    <ei:DataStateBehavior Binding="{Binding IsLoadingData}" Value="true" TrueState="HistoryLoading" FalseState="HistoryLoaded" /> 
</i:Interaction.Behaviors> 
</TheThingYouWantToModify > 
+0

Ich mag diesen Ansatz. Tut mir leid, dass ich so ignorant bin, aber wie bindet man einen visuellen Zustand an eine Eigenschaft? –

+0

Ich glaube, es ist anders zwischen Silverlight und WPF 4.In SL4 können Sie jedoch das DataStateBehavior oder ein DataStateSwitchBehavior verwenden - nicht viele Beispiele, aber genug, um loszulegen. Sie benennen Ihre visuellen Status und fügen interaction.behavior-Knoten den Zielsteuerelementen mit einem datastate-Verhalten hinzu, um in den benannten visuellen Status zu wechseln, der für eine gebundene Eigenschaft ausgelöst wird. –

Verwandte Themen