我正在使用UWP中的自定义数字选择器控件,并尝试将视图模型绑定到其SelectedValue属性。目前,即使使用双向绑定并将更新触发器设置为PropertyChanged
,我的绑定也无法在任何一个方向上运行。目前我已经使用事件处理程序解决了这个问题,但是我想将这个控件分解为一个库,用于我们公司的自定义控件,并且可以直接使用它。以下是我的控制代码和我使用控件的页面的基本代码:
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);
}
}
}
Page.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 :(得分:1)
我做了一些调查,这是我发现的:
NumberPicker
来自ItemsControl
,则双向绑定将不起作用。如果它来自UserControl
,那么双向绑定将起作用。NumberPicker
作为UserControl
(通过右键单击项目&gt;添加&gt;新项目&gt;用户控件),但随后更改了基类{{1到UserControl
。虽然这不一定是坏事,但在这种情况下,它似乎打破了双向绑定(最终是因为ItemsControl
调用它在构造函数中的Application.LoadComponent()
内执行)。相反,您应该创建一个模板化控件,它通过创建.cs代码文件来工作,控件的InitializeComponent()
的XAML将进入Themes / Generic.xaml。如果以这种方式组织控件,双向绑定应该有效。关于视图模型,还有一些我想指出的事项:
DefaultStyle
,这意味着对视图模型属性的更改不会传播到INotifyPropertyChanged
。如果您希望发生这种情况,绑定源(视图模型)必须通过NumberPicker
接口支持属性更改事件,或者属性必须是INotifyPropertyChanged
。DependencyProperty
,那么您还需要更新INotifyPropertyChanged
类以更新视图,以响应您当前没有执行的NumberPicker
属性的更改。在这种情况下,您只是在控件上引发SelectedValue
事件,您需要滚动SelectionChanged
以匹配新值。