読者です 読者をやめる 読者になる 読者になる

しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Xamarin で ItemsControl っぽいコントロールを作りたい(2)

Xamarin Windows ランタイムアプリ C# XAML Windows Phone

前回 で ItemsControl っぽいものを作りましたが、さらに ItemsPanel を切り替えるようにしてみます

f:id:matatabi_ux:20150125164020p:plain

修正対象は上記の通り

まずは ItemsControl

/// <summary>
/// ItemsControl 風 View
/// </summary>
public class ItemsControl : ContentView
{
    #region ItemsPanel

    /// <summary>
    /// ItemsPanel BindableProperty
    /// </summary>
    public static readonly BindableProperty ItemsPanelProperty = BindableProperty.Create<ItemsControl, Layout<View>>(
        p => p.ItemsPanel,
        new StackLayout(),
        BindingMode.OneWay,
        null,
        OnItemsPanelChanged);

    /// <summary>
    /// ItemsPanel CLR プロパティ
    /// </summary>
    public Layout<View> ItemsPanel
    {
        get { return (Layout<View>)this.GetValue(ItemsPanelProperty); }
        set { this.SetValue(ItemsPanelProperty, value); }
    }

    /// <summary>
    /// ItemsPanel 変更イベントハンドラ
    /// </summary>
    /// <param name="bindable">BindableObject</param>
    /// <param name="oldValue">古い値</param>
    /// <param name="newValue">新しい値</param>
    private static void OnItemsPanelChanged(BindableObject bindable, Layout<View> oldValue, Layout<View> newValue)
    {
        var control = bindable as ItemsControl;
        if (control == null)
        {
            return;
        }

        if (oldValue != null)
        {
            oldValue.Children.Clear();
        }

        if (newValue == null)
        {
            return;
        }

        foreach (var item in control.ItemsSource)
        {
            var content = control.ItemTemplate.CreateContent();
            View view;
            var cell = content as ViewCell;
            if (cell != null)
            {
                view = cell.View;
            }
            else
            {
                view = (View)content;
            }

            view.BindingContext = item;
            control.ItemsPanel.Children.Add(view);
        }

        control.Content = newValue;
        control.UpdateChildrenLayout();
        control.InvalidateLayout();
    }

    #endregion //ItemsPanel

    ~ 中略 ~

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public ItemsControl()
    {
        this.Content = this.ItemsPanel;
    }

    ~ 中略 ~
}

ItemsPanel の部分を変更しました

そして ViewModel

/// <summary>
/// トップ画面の ViewModel
/// </summary>
public class TopPageViewModel : BindableBase
{
    /// <summary>
    /// アイテム
    /// </summary>
    private ObservableCollection<ItemViewModel> items;

    /// <summary>
    /// アイテム
    /// </summary>
    public ObservableCollection<ItemViewModel> Items
    {
        get { return this.items; }
        set { this.SetProperty<ObservableCollection<ItemViewModel>>(ref this.items, value); }
    }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public TopPageViewModel()
    {
        this.items = new ObservableCollection<ItemViewModel>()
        {
            new ItemViewModel()
            {
                Content = "Top",
                XConstraint = Constraint.RelativeToParent((p) => { return p.Width / 2 - 30; }),
                YConstraint = Constraint.Constant(0),
            },
            new ItemViewModel()
            {
                Content = "Left",
                XConstraint = Constraint.Constant(0),
                YConstraint = Constraint.RelativeToParent((p) => { return p.Height / 2 - 40; }),
            },
            new ItemViewModel()
            {
                Content = "Right",
                XConstraint = Constraint.RelativeToParent((p) => { return p.Width - 100; }),
                YConstraint = Constraint.RelativeToParent((p) => { return p.Height / 2 - 40; }),
            },
            new ItemViewModel()
            {
                Content = "Bottom",
                XConstraint = Constraint.RelativeToParent((p) => { return p.Width / 2 - 50; }),
                YConstraint = Constraint.RelativeToParent((p) => { return p.Height - 40; }),
            },
        };
    }
}

ちょっとデータを変えてます

ItemViewModel はプロパティのみのただの ViewModel なので割愛

最後に XAML

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:b="clr-namespace:XamarinControl.Behaviors;assembly=XamarinControl"
             xmlns:c="clr-namespace:XamarinControl.Controls;assembly=XamarinControl"
             xmlns:vm="clr-namespace:XamarinControl.ViewModels;assembly=XamarinControl"
             x:Class="XamarinControl.Views.TopPage">

  <ContentPage.BindingContext>
    <vm:TopPageViewModel/>
  </ContentPage.BindingContext>
  
  <c:ItemsControl ItemsSource ="{Binding Items}">
    <c:ItemsControl.ItemsPanel>
      <RelativeLayout/>
    </c:ItemsControl.ItemsPanel>
    <c:ItemsControl.ItemTemplate>
      <DataTemplate>
        <Grid Padding="20,10" 
              RelativeLayout.XConstraint="{Binding XConstraint}"
              RelativeLayout.YConstraint="{Binding YConstraint}">
          <Label Text="{Binding Content}"/>
        </Grid>
      </DataTemplate>
    </c:ItemsControl.ItemTemplate>
  </c:ItemsControl>
</ContentPage>

デフォルトの StackLayout の代わりに RelativeLayout を設定しています

これで実行すると・・・

f:id:matatabi_ux:20150125164541p:plain

相対位置でレイアウトされるようになりました