MVVM : Creating Reusable Flyout Control Composite

In previous article, I described that a Flyout control composed by a TextBox control and a Button control is able to relay a value to a DataContext of a ListView control. It is a useful mechanism as substitute a ComboBox control. Because some processings are able to run before an item is added to a ListView control.

I want to use it many times, so make the mechanism to generic reusable composite. This article describe how to create reusable Flyout control composite.

Sample solution

・Outline

Basic mechanism is same as the sample code of previous article. A difference about XAML is a Name property of the controls set nothing for it would be reused many times.

The side of a view model, a Flyout control is showed from a FlyoutBase in order to divide from individual XAML controls. though a button control in the Flyout control should close the Flyout that includes self.

To do it, use a Command property and a CommandParameter property of a Button control. The value bind to the Command property of a Button control should extend by the ICommand interface. An Execute method of it take an argument represented by the CommandParameter property of the Button control. When the CommandParameter is set a RelativeSource with the “Mode=Self” condition, it transfer the button itself as the argument of the method.

Two ways following is how to close a Flyout control from a button which the Flyout control includes.

1. When a Button control show a Flyout control, the Button control set itself to the private variable of a view model. When the Button control close the Flyout control, the Button control use the variable to determines the Flyout control.

2. The button close the flyout aquire the flyout from the VisualTreeHelper class.

The sample code of this article is includes the first way.

These two method are below.
1. The sample solution includes.

XAML
<StackPanel DataContext=”{Binding GenreDataContext}” Grid.Row=”1″ Grid.Column=”1″ Margin=”10,10,0,0″ >
  <ListView BorderBrush=”White” BorderThickness=”1″ Header=”Genre List” ItemsSource=”{Binding GenericListViewItemsSource}” Height=”600″>
    <ListView.ItemTemplate>
      <DataTemplate>
        <StackPanel Orientation=”Horizontal”>
          <TextBlock Text=”{Binding FirstItemText}”/>
        </StackPanel>
      </DataTemplate>
    </ListView.ItemTemplate>
  </ListView>
  <Button Content=”Enter if it is not in the list” HorizontalAlignment=”Center” Command=”{Binding ShowFlyoutButtonCommand}” CommandParameter=”{Binding RelativeSource={RelativeSource Mode=Self}}” Margin=”0,10,0,0″>
    <FlyoutBase.AttachedFlyout>
      <Flyout>
        <StackPanel>
          <StackPanel Orientation=”Horizontal”>
            <TextBox Header=”{Binding FirstTextBoxHeader}” Text=”{Binding FirstTextBoxText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}” Width=”300″/>
          </StackPanel>
          <Button Content=”O K” HorizontalAlignment=”Right” Margin=”0,10,0,0″ Command=”{Binding CloseFlyoutButtonCommand}”/>
        </StackPanel>
      </Flyout>
    </FlyoutBase.AttachedFlyout>
  </Button>
</StackPanel>

View model( This code is a part of the view model that binds to the Command property of the Button control.)
Button currentButton { get; set; }
class executeFlyoutCommand : ICommand
{
  public Action<object> ExecuteAction { get; set; }
  public bool CanExecute(object parameter) { return true; }
  public event EventHandler CanExecuteChanged;
  public void OnCanExecuteChanged()
  {
    if (CanExecuteChanged != null) CanExecuteChanged(this, null);
  }
  public void Execute(object parameter) { ExecuteAction(parameter); }
}
executeFlyoutCommand showFlyoutButtonCommand { get; set; }
public ICommand ShowFlyoutButtonCommand
{
  get
  {
    if (showFlyoutButtonCommand == null)
    {
      showFlyoutButtonCommand = new executeFlyoutCommand();
      showFlyoutButtonCommand.ExecuteAction = (p) =>
      {
        currentButton = p as Button;
        if (currentButton != null) FlyoutBase.ShowAttachedFlyout(currentButton);
      };
    }
    return showFlyoutButtonCommand;
  }
}
executeFlyoutCommand closeFlyoutButtonCommand { get; set; }
public ICommand CloseFlyoutButtonCommand
{
  get
  {
    if (closeFlyoutButtonCommand == null)
    {
      closeFlyoutButtonCommand = new executeFlyoutCommand();
      closeFlyoutButtonCommand.ExecuteAction = (b) =>
      {
        Flyout f = FlyoutBase.GetAttachedFlyout(currentButton) as Flyout;
        if (f != null) f.Hide();
        if (%insertValue% != null)
        {
          GenericListViewItemsSource.Add(%insertValue%);
          RaisePropertyChanged(“GenericListViewItemsSource”);
        }
      };
    }
    return closeFlyoutButtonCommand;
  }
}

2. Using the VisualTreeHelper class.

XAML is same as above code with exclude a part below.
<StackPanel DataContext=”{Binding GenreDataContext}” Grid.Row=”1″ Grid.Column=”1″ Margin=”10,10,0,0″ >
  <ListView BorderBrush=”White” BorderThickness=”1″ Header=”Genre List” ItemsSource=”{Binding GenericListViewItemsSource}” Height=”600″>
    <ListView.ItemTemplate>
    …
          <Button Content=”O K” HorizontalAlignment=”Right” Margin=”0,10,0,0″ Command=”{Binding CloseFlyoutButtonCommand}”/>
          <Button Content=”Using VisualTreeHelper” HorizontalAlignment=”Right” Margin=”0,10,0,0″ Command=”{Binding CloseUsingVisualTreeFlyoutButtonCommand}”/>
    …
</StackPanel>

View model( Add the Command property of the Button control, like below.)
executeFlyoutCommand closeUsingVisualTreeFlyoutButtonCommand { get; set; }
public ICommand CloseUsingVisualTreeFlyoutButtonCommand
{
  get
  {
    if (closeUsingVisualTreeFlyoutButtonCommand == null)
    {
      closeUsingVisualTreeFlyoutButtonCommand = new executeFlyoutCommand();
      closeUsingVisualTreeFlyoutButtonCommand.ExecuteAction = (b) =>
      {
        IReadOnlyList<Popup> popups = VisualTreeHelper.GetOpenPopups(Window.Current);
        foreach (Popup p in popups) p.IsOpen = false;
        if (%insertValue% != null)
        {
          GenericListViewItemsSource.Add(%insertValue%);
          RaisePropertyChanged(“GenericListViewItemsSource”);
        }
      };
    }
    return closeUsingVisualTreeFlyoutButtonCommand;
  }
}

・View model

Reusable Flyout composite is provided by the DataContext property of a portion of XAML intent to bind to individual area of the contents semantically. It is like such as a contents of Song, Genre or Artist. These information can build by words of from one to three roughly. Therefore I prepare generic type for “%insertValue%” of above code such as following.

public class GenericListItem
{
  public string FirstItemText { get; set; }
  public string SecondItemText { get; set; }
  public string LastItemText { get; set; }
}
GenericListItem insertValue { get; set; }
public ObservableCollection<GenericListItem> GenericListViewItemsSource{ get; set; }

The DataContext of reusable Flyout composite is structured with command set ( above), generic type ( above), and properties of TextBox controls( below).

public class GenericDataContext : INotifyPropertyChanged
{
  // This is generic type explained at above
  public class GenericListItem
  GenericListItem insertValue { get; set; }
  public ObservableCollection<GenericListItem> GenericListViewItemsSource{ get; set; }
  // this is properties of TextBox controls.
  public string FirstTextBoxHeader { get; set; }
  string firstTextBoxText { get; set; }
  public string FirstTextBoxText
  {
    get
    {
      return firstTextBoxText;
    }
    set
    {
      firstTextBoxText = value;
      insertValue = insertValue ?? new GenericListItem();
      insertValue.FirstItemText = firstTextBoxText;
    }
  }
  public string SecondTextBoxHeader { get; set; }
  string secondTextBoxText { get; set; }
  public string SecondTextBoxText
  {
    get
    {
      return secondTextBoxText;
    }
    set
    {
      secondTextBoxText = value;
      insertValue = insertValue ?? new GenericListItem();
      insertValue.SecondItemText = secondTextBoxText;
    }
  }
  public string LastTextBoxHeader { get; set; }
  string lastTextBoxText { get; set; }
  public string LastTextBoxText
  {
    get
    {
      return lastTextBoxText;
    }
    set
    {
      lastTextBoxText = value;
      insertValue = insertValue ?? new GenericListItem();
      insertValue.LastItemText = lastTextBoxText;
    }
  }
  // This is command set explained at above
  Button currentButton { get; set; }
  class executeFlyoutCommand : ICommand
  executeFlyoutCommand showFlyoutButtonCommand { get; set; }
  public ICommand ShowFlyoutButtonCommand…
  executeFlyoutCommand closeFlyoutButtonCommand { get; set; }
  public ICommand CloseFlyoutButtonCommand…
  executeFlyoutCommand closeUsingVisualTreeFlyoutButtonCommand { get; set; }
  public ICommand CloseUsingVisualTreeFlyoutButtonCommand…
  // Implement the PropertyChanged interface.
  public event PropertyChangedEventHandler PropertyChanged;
  protected void RaisePropertyChanged(string name)
  {
    if(PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(name));
  }
}

Same such as the Genre XAML area of above code, this generic class can reuse for the DataContext bind to individual an Artist XAML area. See below

public class MainPageViewModel:INotifyPropertyChanged
{
  public class GenericDataContext : INotifyPropertyChanged
  public class ArtistViewModel : GenericDataContext
  {
    ObservableCollection<ArtistModel> artists { get; set; }
    public ObservableCollection<ArtistModel> Artists
    {
      get
      {
        return artists;
      }
      set
      {
        GenericListViewItemsSource = GenericListViewItemsSource ?? new ObservableCollection<GenericListItem>();
        GenericListItem g;
        artists = value;
        foreach(ArtistModel a in artists)
        {
          g = new GenericListItem();
          g.FirstItemText = a.FirstName;
          g.LastItemText = a.LastName;
          GenericListViewItemsSource.Add(g);
        }
      }
    }
    public ArtistViewModel()
    {
      FirstTextBoxHeader = “First Name: “;// Maybe this is got from the resource for globalize.
      LastTextBoxHeader = “Last Name: “;// Maybe this is got from the resource for globalize.
    }
  }
  public ArtistViewModel ArtistDataContext { get; set; }
  public class SongViewModel : GenericDataContext
  public SongViewModel SongDataContext { get; set; }
  public class GenreViewModel : GenericDataContext
  public GenreViewModel GenreDataContext { get; set; }
  public MainPageViewModel()…
  public event PropertyChangedEventHandler PropertyChanged;
  protected void RaisePropertyChanged(string name)…
}

And in the constructor of the DataContext property of the Page, create an instance of the DataContext for individual XAML area.

Please reference sample code about the part of following code that obtain the data of stub or local data.

public MainPageViewModel()
{
  DataAccessLayer dal = new DataAccessLayer ();
  ObservableCollection<ArtistModel > artists = new ObservableCollection< ArtistModel> artists = dal.GetLocalData<ObservableCollection<ArtistModel>>().Result;
  if (artists.Count == 0) artists = dal.GetStubArtistData();
  ArtistDataContext = new ArtistViewModel() { Artists = artists };
}

About takao