UWP自定义控件绑定在任何方向上都不起作用
本文关键字:不起作用 方向 任何 自定义控件 绑定 UWP | 更新日期: 2023-09-27 18:11:56
我正在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; }
}
我做了一些调查,发现如下:
- 如果
NumberPicker
来自ItemsControl
,则双向绑定不起作用。如果它是从UserControl
派生的,那么双向绑定将工作。 - 它看起来好像你最初创建的
NumberPicker
作为UserControl
(通过右键单击项目>添加>新建项目>用户控制),但随后改变了基类从UserControl
到ItemsControl
。虽然这不一定是一件坏事,但在这种情况下,它似乎破坏了双向绑定(最终是因为它在构造函数的InitializeComponent()
内部调用Application.LoadComponent()
)。相反,您应该创建一个模板控件,它通过创建一个.cs代码文件来工作,并且控件的DefaultStyle
的XAML将进入Themes/Generic.xaml。如果您以这种方式组织控件,则双向绑定应该可以工作。
关于视图模型,我还想指出几件事:
- 你的视图模型类没有实现
INotifyPropertyChanged
,这意味着对视图模型属性的更改不会传播到NumberPicker
。如果你想这样做,绑定源(视图模型)必须通过INotifyPropertyChanged
接口支持属性更改事件,或者属性必须是DependencyProperty
。 - 如果你确实实现了
INotifyPropertyChanged
,那么你还需要更新你的NumberPicker
类来更新视图以响应SelectedValue
属性的变化,这是你目前没有做的。在这种情况下,您只在控件上引发SelectionChanged
事件,您需要滚动ScrollViewer
以匹配新值。