2016-10-06 4 views
0

UWPでカスタム数値ピッカーコントロールを使用していて、ビューモデルをSelectedValueプロパティにバインドしようとしています。現在のところ、双方向バインディングと更新トリガーがPropertyChangedに設定されていても、私のバインドはいずれの方向にも機能しません。今はイベントハンドラを使って作業してきましたが、私はこのコントロールを会社のカスタムコントロール用のライブラリに分けて、すぐに使用できるようにしたいと考えています。私の制御コードと私はコントロールを使用しているページの基本的なコードは以下の通りです:UWPカスタムコントロールバインディングがどちらの方向にも動作しません

NumberPicker.xaml:

<ItemsControl 
    x:Class="UWPApp.Scorekeeper.NumberPicker" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="using:UWPApp.Scorekeeper" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:vms="using:UWPApp.Scorekeeper.Models.ViewModels" 
    mc:Ignorable="d" 
    x:Name="Select" 
    Loaded="Select_Loaded" 
    ItemsSource="{x:Bind ItemsCollection}" 
    d:DesignHeight="300" 
    d:DesignWidth="400"> 
    <ItemsControl.ItemTemplate> 
     <DataTemplate x:DataType="local:NumberItem"> 
      <Viewbox HorizontalAlignment="Stretch" Height="115"> 
       <TextBlock Text="{x:Bind Value}"></TextBlock> 
      </Viewbox> 
     </DataTemplate> 
    </ItemsControl.ItemTemplate> 
    <ItemsControl.Template> 
      <ControlTemplate> 
       <Grid BorderThickness="4" BorderBrush="Black"> 
        <Grid.RowDefinitions> 
         <RowDefinition Height="1*"/> 
         <RowDefinition Height="2*"/> 
         <RowDefinition Height="1*"/> 
        </Grid.RowDefinitions> 
        <Rectangle Opacity=".5"> 
         <Rectangle.Fill> 
          <LinearGradientBrush StartPoint=".5,0" EndPoint=".5,1"> 
           <GradientStop Offset="0" Color="Black"/> 
           <GradientStop Offset="1" Color="Transparent"/> 
          </LinearGradientBrush> 
         </Rectangle.Fill> 
        </Rectangle> 
        <ScrollViewer Grid.RowSpan="3" ViewChanged="Select_ViewChanged" VerticalSnapPointsType="Mandatory" VerticalSnapPointsAlignment="Center" x:Name="MinutesSelect" HorizontalScrollMode="Disabled" VerticalScrollMode="Auto" VerticalScrollBarVisibility="Visible"> 
         <ItemsPresenter></ItemsPresenter> 
        </ScrollViewer> 
        <Rectangle Grid.Row="2" Opacity=".5"> 
         <Rectangle.Fill> 
          <LinearGradientBrush StartPoint=".5,1" EndPoint=".5,0"> 
           <GradientStop Offset="0" Color="Black"/> 
           <GradientStop Offset="1" Color="Transparent"/> 
          </LinearGradientBrush> 
         </Rectangle.Fill> 
        </Rectangle> 
       </Grid> 
      </ControlTemplate> 
     </ItemsControl.Template> 
    <ItemsControl.ItemsPanel> 
     <ItemsPanelTemplate> 
      <StackPanel Orientation="Vertical"> 
      </StackPanel> 
     </ItemsPanelTemplate> 
    </ItemsControl.ItemsPanel> 
</ItemsControl> 

NumberPicker.xaml.cs:

using System; 
using System.Collections.Generic; 
using System.Collections.ObjectModel; 
using System.Linq; 
using Windows.UI.Core; 
using Windows.UI.Xaml; 
using Windows.UI.Xaml.Controls; 
using UWPApp.Scorekeeper.Models.ViewModels; 
using UWPApp.Scorekeeper.Toolbox; 


namespace UWPApp.Scorekeeper 
{ 
    public class NumberItem 
{ 
    public NumberItem(int? value) 
    { 
     Value = value; 
    } 
    public int? Value { get; set; } 
} 

public sealed partial class NumberPicker : ItemsControl 
{ 


    public event SelectionChangedEventHandler SelectionChanged; 

    public int RangeBottom { get; set; } 

    public int RangeTop { get; set; } 

    public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register("SelectedValue", typeof(int?), typeof(NumberPicker), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedValueChanged))); 

    public static readonly DependencyProperty SelectionChangedProperty = DependencyProperty.Register("SelectionChanged", typeof(SelectionChangedEventHandler), typeof(NumberPicker), new PropertyMetadata(null)); 

    private static void OnSelectedValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var picker = d as NumberPicker; 
     picker.SelectionChanged?.Invoke(picker, new SelectionChangedEventArgs(new List<object> { e.OldValue }, new List<object> { e.NewValue })); 
     return; 
    } 

    public int? SelectedValue { get { return (int?)GetValue(SelectedValueProperty); } set { SetValue(SelectedValueProperty, value); } } 

    public ObservableCollection<NumberItem> ItemsCollection { get; set; } 

    public NumberPicker() 
    { 
     this.InitializeComponent(); 
     DataContext = this; 
     ItemsCollection = new ObservableCollection<NumberItem>(); 
    } 

    private void Select_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e) 
    { 
     if (!e.IsIntermediate) 
     { 
      var scroll = sender as ScrollViewer; 
      var position = scroll.VerticalOffset; 
      var value = Math.Floor(position/115d); 
      SelectedValue = ((int)value); 
     } 
    } 

    private void Select_Loaded(object sender, RoutedEventArgs e) 
    { 
     var count = RangeTop - RangeBottom + 1; 
     var items = Enumerable.Range(RangeBottom, count).Select(m => new NumberItem(m)).ToList(); 
     foreach (var item in items) 
     { 
      ItemsCollection.Add(item); 
     } 
     ItemsCollection.Insert(0, new NumberItem(null)); 
     ItemsCollection.Add(new NumberItem(null)); 
     var period = TimeSpan.FromMilliseconds(10); 
     Windows.System.Threading.ThreadPoolTimer.CreateTimer(async (source) => 
     { 
      await Dispatcher.RunAsync(CoreDispatcherPriority.Normal,() => 
      { 
       var scroll = Select.FindFirstChild<ScrollViewer>(); 
       if (SelectedValue != null) 
       { 
        var position = SelectedValue * 115d + 81.5; 
        scroll.ChangeView(null, position, null, true); 
       } 
      }); 
     }, period); 
    } 
} 
} 

ます。 XAML:

<Page 
    x:Class="UWPApp.Scorekeeper.SelectPenaltyTime" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:local="using:UWPApp.Scorekeeper" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:vms="using:UWPApp.Scorekeeper.Models.ViewModels" 
    mc:Ignorable="d" 
    x:Name="PageElement" 
    Background="{ThemeResource SystemControlBackgroundAccentBrush}" 
    d:DesignHeight="600" 
    d:DesignWidth="1024"> 

    <ContentPresenter x:Name="MainContent" Margin="0,0,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> 
     <Grid Background="{ThemeResource SystemControlBackgroundAccentBrush}"> 
      <Grid.RowDefinitions> 
       <RowDefinition Height="110*"/> 
       <RowDefinition Height="43*"/> 
       <RowDefinition Height="47*"/> 
      </Grid.RowDefinitions> 
      <local:NumberPicker Margin="100,0,750,0" RangeBottom="0" RangeTop="20" SelectedValue="{Binding ElementName=PageElement,Path=ViewModel.Minutes,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"></local:NumberPicker> 
     </Grid> 
    </ContentPresenter> 
</Page> 

Page.xaml.cs:

using System; 
using System.Collections.ObjectModel; 
using System.ComponentModel; 
using System.Linq; 
using System.Runtime.CompilerServices; 
using System.Windows.Input; 
using Windows.UI.Popups; 
using Windows.UI.Xaml; 
using Windows.UI.Xaml.Controls; 
using Windows.UI.Xaml.Navigation; 
using UWPApp.Scorekeeper.Models.TransportClasses; 
using UWPApp.Scorekeeper.Models.ViewModels; 

namespace UWPApp.Scorekeeper 
{ 
    public sealed partial class SelectPenaltyTime : Page 
    { 
     public GameStateModel StateModel { get; set; } 

     public AddPenalty_FVM ViewModel { get; set; } 

     public SelectPenaltyTime() 
     { 
      this.InitializeComponent(); 
     } 

     protected override void OnNavigatedTo(NavigationEventArgs e) 
     { 
      var message = e.Parameter as PenaltyMessage; 
      StateModel = message.StateModel; 
      ViewModel = message.ViewModel; 
     } 

     private void NumberPicker_Loaded(object sender, RoutedEventArgs e) 
     { 
      var picker = sender as NumberPicker; 
      picker.SelectedValue = ViewModel.Minutes; 
     } 

     private void NumberPicker_SelectionChanged(object sender, SelectionChangedEventArgs e) 
     { 
      var picker = sender as NumberPicker; 
      ViewModel.Minutes = picker.SelectedValue; 
     } 
    } 
} 

AddPenalty_FVM:

public class AddPenalty_FVM 
    { 
     public int? Minutes { get; set; } 
    } 
+0

あなたのViewModelはどこですか?INotifyPropertyインターフェイスを実装していますか? – RTDev

+0

それは必要ないはずですよね?値はページが読み込まれたときに読み込まれるだけなので、つまり、セレクタがviewmodelプロパティを設定するだけです。 – Ceshion

+0

これは 'x:Bind'で正しく動作するので、これは' Binding'の制限です。 – Ceshion

答えて

1

私が調査し、ここで私が発見した何のビットをした:

  • NumberPickerItemsControlから派生した場合は、双方向バインディングが動作しません。代わりにUserControlから派生する場合は、双方向バインディングが機能します。あなたは、もともととしてNumberPickerを作成したかのよう
  • に見えUserControlが、その後UserControlからItemsControlに基本クラスを変更(右クリックプロジェクト>追加]> [新しい項目]> [ユーザーコントロールを経由して)。これは必ずしも悪いことではありませんが、この場合は双方向バインディングが壊れているように見えます(最終的にはApplication.LoadComponent()というコールがコンストラクタ内でInitializeComponent()と呼ばれるためです)。代わりにテンプレートコントロールを作成する必要があります。これは.csコードファイルを作成し、コントロールのDefaultStyleのXAMLはThemes/Generic.xamlに入ります。このようにコントロールを整理すると、双方向バインディングが機能するはずです。

私はビューモデルについて指摘したいと思います物事のカップルもあります

  • あなたのビューモデルクラスはビューモデルのプロパティへの変更がないことを意味しINotifyPropertyChangedを実装していません。 NumberPickerに伝播します。これを実現するには、バインディングのソース(ビューモデル)がINotifyPropertyChangedインターフェイスを介してプロパティ変更イベントをサポートする必要があります。または、プロパティがDependencyPropertyである必要があります。
  • INotifyPropertyChangedを実装する場合は、NumberPickerクラスを更新して、現在実行していないSelectedValueプロパティの変更に対応してビューを更新する必要があります。この状況でコントロールのSelectionChangedイベントを発生させるだけでは、ScrollViewerをスクロールして新しい値と一致させる必要があります。
+0

分かりましたが、Minutesの値はこのセレクタの外では変わらないので見えませんでしたINotifyPropertyChangedを実装するために必要です。私はOneWayToSourceを使用します...もしUWPがそれを持っていたら。 – Ceshion

+0

しかし、 'Binding'を使うとプロパティは更新されませんが、' x:Bind'は更新されます。 – Ceshion

+0

ああ、バインディングに 'Mode = TwoWay'を指定してから、二方向バインドの仕組みがほしいと思ったところです。 –

関連する問題