2016-10-06 3 views
0

Ich arbeite an einem benutzerdefinierten Zahlenauswahlsteuerelement in UWP, und ich versuche, ein Ansichtsmodell an seine SelectedValue-Eigenschaft zu binden. Auch wenn die Bindung in beide Richtungen und der Aktualisierungstrigger auf PropertyChanged gesetzt sind, funktioniert meine Bindung in keiner Richtung. Momentan habe ich mit Event-Handlern daran gearbeitet, aber ich möchte dieses Steuerelement in eine Bibliothek für benutzerdefinierte Steuerelemente für unser Unternehmen aufteilen und es sofort einsatzbereit machen lassen. Es folgt mein Steuercode und die grundlegenden Code der Seite ich die Kontrolle in bin mit:UWP Benutzerdefinierte Steuerelementbindung funktioniert in keiner Richtung

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); 
    } 
} 
} 

Seite. 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

Wo ist Ihr ViewModel, implementiert es INotifyProperty-Schnittstelle? – RTDev

+0

Es sollte nicht nötig sein, oder? Da der Wert nur geladen wird, wenn die Seite geladen wird?Das heißt, nur der Selektor setzt die Viewmodel-Eigenschaft. – Ceshion

+0

Es scheint, dass dies alles eine Einschränkung von 'Binding' ist, da es korrekt mit' x: Bind' funktioniert. – Ceshion

Antwort

1

habe ich ein bisschen untersucht und hier ist was ich entdeckt:

  • Wenn NumberPicker von ItemsControl leitet, die Zwei-Wege wird nicht funktionieren verbindlich. Wenn es stattdessen von UserControl abgeleitet wird, funktioniert die Zweiwege-Bindung.
  • Es sieht so aus, als hätten Sie ursprünglich NumberPicker als UserControl erstellt (über Rechtsklick-Projekt> Hinzufügen> Neues Element> Benutzerkontrolle), aber dann die Basisklasse von UserControl zu ItemsControl geändert. Während dies nicht unbedingt eine schlechte Sache zu tun ist, in diesem Fall scheint es Zwei-Wege-Bindungen zu brechen (letztlich wegen der Application.LoadComponent() Aufruf es innerhalb InitializeComponent() im Konstruktor tut). Stattdessen sollten Sie ein Templateed Control erstellen, das durch Erstellen einer CS-Codedatei funktioniert, und das XAML für das DefaultStyle des Steuerelements wird in Themes/Generic.xaml gespeichert. Wenn Sie Ihr Steuerelement auf diese Weise organisieren, sollte die beidseitige Bindung funktionieren.

Es gibt auch ein paar Dinge, Ich mag würde in Bezug auf die Ansicht Modell hinweisen:

  • Ihre Ansicht Modellklasse hat INotifyPropertyChanged nicht umsetzen, was bedeutet, dass Änderungen an den Eigenschaften des View-Modell wird nicht propagieren zu der NumberPicker. Wenn dies der Fall sein soll, muss die Quelle der Bindung (das Ansichtsmodell) Eigenschaftenänderungsereignisse über die Schnittstelle INotifyPropertyChanged unterstützen, oder die Eigenschaft muss eine DependencyProperty sein.
  • Wenn Sie INotifyPropertyChanged implementieren, müssen Sie auch Ihre NumberPicker-Klasse aktualisieren, um die Ansicht als Reaktion auf Änderungen in der SelectedValue-Eigenschaft zu aktualisieren, die Sie gerade nicht ausführen. Sie erhöhen nur das Ereignis SelectionChanged auf dem Steuerelement in dieser Situation, müssen Sie die ScrollViewer scrollen, um den neuen Wert zu entsprechen.
+0

Ich bin mir dessen bewusst, dass sich der Wert von Minutes außerhalb dieses Selektors nicht ändert, so dass es nicht schien notwendig, um INotifyPropertyChanged zu implementieren. Ich würde OneWayToSource verwenden ... Wenn UWP es hatte. – Ceshion

+0

Aber ja, mit 'Binding' wird die Eigenschaft nicht aktualisiert, aber mit' x: Bind' tut es das. – Ceshion

+0

Oh OK, ich dachte nur, du wolltest eine bidirektionale Bindemechanik, da du 'Mode = TwoWay' in der Bindung angegeben hast. –

Verwandte Themen