无法在 MVVM UWP 中取消选择 ListView 项目
Can't deselect ListView Item in MVVM UWP
我希望能够单击 ListView 项目,然后将我带到相应的页面。但是由于不存在 ClickedItem
之类的东西与 ItemClick
一起使用,我必须使用 SelectedItem
(获取用户单击的对象)和 SelectionChanged
在它发生时捕获(因为这是以一种方式设置的,当用户单击时,他会进行选择,从而触发此操作)。
因为在 MVVM 中我不能使用事件,所以我将事件绑定到我的 ViewModel 中的方法。
<GridView x:Name="MyGrid"
ItemsSource="{x:Bind ViewModel.myList, Mode=OneWay}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
IsSwipeEnabled="false"
SelectedItem="{Binding mySelectedItem, Mode=TwoWay}" // Binding makes it easier to bind the whole object
SelectionChanged="{x:Bind ViewModel.SelectioMade}"
>
我在 ViewModel 中填写了我的列表。我正在使用 INotifyPropertyChanged 的 Template10 实现。
private MyListItemClass _mySelectedItem;
public MyListItemClass mySelectedItem{
get { return _mySelectedItem; }
set { Set(ref _mySelectedItem, value); }
}
当用户点击某个项目时,这个简单的方法会将我推送到下一页。
public void SelectioMade() {
if (_mySelectedItem != null) {
NavigationService.Navigate(typeof(Views.DetailPage), _mySelectedItem.id);
}
}
有效。
问题是做出了选择并且它仍然存在。当我点击 DetailPage
上的后退按钮时,我在离开时返回到此列表,但点击的项目仍处于选中状态。因此,再次单击它实际上不会进行选择并触发 SelectionChanged
.
明显的选择似乎是在我不再需要该值时将 mySelectedItem
设置为 null
,但它不起作用。
public void SelectioMade() {
if (_mySelectedItem != null) {
NavigationService.Navigate(typeof(Views.DetailPage), _mySelectedItem.id);
mySelectedItem = null;
}
}
我似乎无法将其设置回 null
。如果我在 mySelectedItem = null;
上放置一个断点,它就不会执行任何操作。它确实会触发 set { Set(ref _mySelectedItem, value); }
,但视图不会更新。单击的项目既没有被取消选择,我绑定到 mySelectedItem.id
属性之一的 TextBlock 也没有被更改(或者清空)。
我想知道为什么这不起作用,可能还有解决方法。我的 MVVM 可能并不完美,我还在学习。虽然它可能并不完美,但我并不是真的在寻找如何正确编写 MVVM 的建议。我想知道为什么这不起作用,因为在我看来,它应该可以正常工作。
我的第一直觉是检查您的 Set 方法,以确保它确实向视图发送了正确的通知。我不熟悉 Template10 实现,所以我觉得您不需要为 Set() 提供 属性 名称似乎很奇怪。
除此之外,我建议您重新使用 Click 而不是 SelectionChanged,因为这是您真正感兴趣的行为。您应该阅读一些有关附加属性的内容,这是完成任务的好方法这通常需要代码隐藏而不实际使用代码隐藏。它们使 MVVM 更加实用,而且不那么骇人听闻。
https://msdn.microsoft.com/en-us/library/ms749011(v=vs.110).aspx
附加的 属性 或多或少允许您定义可以附加到 XAML 中任何元素的 DependencyProperty,例如您的 GridView。因为您可以访问 setter 中的元素,所以您可以自由地附加到它的事件。因此,您可以创建一个带有委托类型的附加 属性,它会将点击等事件转发给委托。回到视图中,您将它绑定到 ViewModel 中的处理程序,如下所示:
<GridView something:MyAttachedProperties.ClickHandler="{Binding MyClickHandler}" />
希望对您有所帮助!
SelectedIndex = -1,在您的 SelectedItem 空集之后 属性?所以是的,另一个 属性 将是必需的,或者确保该页面的缓存也被禁用。
GridView 似乎不喜欢在 SelectionChanged 处理程序中更改 SelectedItem 属性(如果不使用守卫,可能会导致无限循环)。您可以改为在该页面的 OnNavigatedTo 处理程序中将 SelectedItem 设置为 null(或者任何等效的模板 10)。
此外,您实际上并不需要订阅 SelectionChanged 事件,因为您可以在 mySelectedItem 属性.
的 setter 中检测到该事件
但是,我认为通过监听选择更改事件来处理项目点击是错误的,因为选择可以通过其他方式更改(例如 up/down 箭头键或 Tab 键)。你想做的就是响应一个项目点击并获取点击的项目,对吧?为此,您可以 x:Bind 将 ItemClick 事件发送到视图模型中的方法:
<GridView ItemClick="{x:Bind ViewModel.ItemClick}" SelectionMode="None" IsItemClickEnabled="True">
public void ItemClick(object sender, ItemClickEventArgs e)
{
var item = e.ClickedItem;
}
如果您对视图模型中的 ItemClick 方法签名感到不安,那么您可以创建自己的 ItemClick 行为来执行视图模型中公开的命令,并将命令的参数绑定到被单击的项目。
如果您出于某种原因不使用行为,那么您可以制作自己的附件 属性,如下所示:
public class ViewHelpers
{
#region ItemClickCommand
public static readonly DependencyProperty ItemClickCommandProperty =
DependencyProperty.RegisterAttached("ItemClickCommand", typeof(ICommand), typeof(ViewHelpers), new PropertyMetadata(null, onItemClickCommandPropertyChanged));
public static void SetItemClickCommand(DependencyObject d, ICommand value)
{
d.SetValue(ItemClickCommandProperty, value);
}
public static ICommand GetItemClickCommand(DependencyObject d)
{
return (ICommand)d.GetValue(ItemClickCommandProperty);
}
static void onItemClickCommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listView = d as ListViewBase;
if (listView == null)
throw new Exception("Dependency object must be a ListViewBase");
listView.ItemClick -= onItemClick;
listView.ItemClick += onItemClick;
}
static void onItemClick(object sender, ItemClickEventArgs e)
{
var listView = sender as ListViewBase;
var command = GetItemClickCommand(listView);
if (command != null && command.CanExecute(e.ClickedItem))
command.Execute(e.ClickedItem);
}
#endregion
}
XAML 不需要使用 MVVM 模式,这意味着您需要自己编写很多 "missing" 功能来使 MVVM 对您来说更容易(如上面的 ItemClick 附件属性)。也许模板 10 已经为您提供了一些行为?我不熟悉。
我希望能够单击 ListView 项目,然后将我带到相应的页面。但是由于不存在 ClickedItem
之类的东西与 ItemClick
一起使用,我必须使用 SelectedItem
(获取用户单击的对象)和 SelectionChanged
在它发生时捕获(因为这是以一种方式设置的,当用户单击时,他会进行选择,从而触发此操作)。
因为在 MVVM 中我不能使用事件,所以我将事件绑定到我的 ViewModel 中的方法。
<GridView x:Name="MyGrid"
ItemsSource="{x:Bind ViewModel.myList, Mode=OneWay}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
IsSwipeEnabled="false"
SelectedItem="{Binding mySelectedItem, Mode=TwoWay}" // Binding makes it easier to bind the whole object
SelectionChanged="{x:Bind ViewModel.SelectioMade}"
>
我在 ViewModel 中填写了我的列表。我正在使用 INotifyPropertyChanged 的 Template10 实现。
private MyListItemClass _mySelectedItem;
public MyListItemClass mySelectedItem{
get { return _mySelectedItem; }
set { Set(ref _mySelectedItem, value); }
}
当用户点击某个项目时,这个简单的方法会将我推送到下一页。
public void SelectioMade() {
if (_mySelectedItem != null) {
NavigationService.Navigate(typeof(Views.DetailPage), _mySelectedItem.id);
}
}
有效。
问题是做出了选择并且它仍然存在。当我点击 DetailPage
上的后退按钮时,我在离开时返回到此列表,但点击的项目仍处于选中状态。因此,再次单击它实际上不会进行选择并触发 SelectionChanged
.
明显的选择似乎是在我不再需要该值时将 mySelectedItem
设置为 null
,但它不起作用。
public void SelectioMade() {
if (_mySelectedItem != null) {
NavigationService.Navigate(typeof(Views.DetailPage), _mySelectedItem.id);
mySelectedItem = null;
}
}
我似乎无法将其设置回 null
。如果我在 mySelectedItem = null;
上放置一个断点,它就不会执行任何操作。它确实会触发 set { Set(ref _mySelectedItem, value); }
,但视图不会更新。单击的项目既没有被取消选择,我绑定到 mySelectedItem.id
属性之一的 TextBlock 也没有被更改(或者清空)。
我想知道为什么这不起作用,可能还有解决方法。我的 MVVM 可能并不完美,我还在学习。虽然它可能并不完美,但我并不是真的在寻找如何正确编写 MVVM 的建议。我想知道为什么这不起作用,因为在我看来,它应该可以正常工作。
我的第一直觉是检查您的 Set 方法,以确保它确实向视图发送了正确的通知。我不熟悉 Template10 实现,所以我觉得您不需要为 Set() 提供 属性 名称似乎很奇怪。
除此之外,我建议您重新使用 Click 而不是 SelectionChanged,因为这是您真正感兴趣的行为。您应该阅读一些有关附加属性的内容,这是完成任务的好方法这通常需要代码隐藏而不实际使用代码隐藏。它们使 MVVM 更加实用,而且不那么骇人听闻。
https://msdn.microsoft.com/en-us/library/ms749011(v=vs.110).aspx
附加的 属性 或多或少允许您定义可以附加到 XAML 中任何元素的 DependencyProperty,例如您的 GridView。因为您可以访问 setter 中的元素,所以您可以自由地附加到它的事件。因此,您可以创建一个带有委托类型的附加 属性,它会将点击等事件转发给委托。回到视图中,您将它绑定到 ViewModel 中的处理程序,如下所示:
<GridView something:MyAttachedProperties.ClickHandler="{Binding MyClickHandler}" />
希望对您有所帮助!
SelectedIndex = -1,在您的 SelectedItem 空集之后 属性?所以是的,另一个 属性 将是必需的,或者确保该页面的缓存也被禁用。
GridView 似乎不喜欢在 SelectionChanged 处理程序中更改 SelectedItem 属性(如果不使用守卫,可能会导致无限循环)。您可以改为在该页面的 OnNavigatedTo 处理程序中将 SelectedItem 设置为 null(或者任何等效的模板 10)。
此外,您实际上并不需要订阅 SelectionChanged 事件,因为您可以在 mySelectedItem 属性.
的 setter 中检测到该事件但是,我认为通过监听选择更改事件来处理项目点击是错误的,因为选择可以通过其他方式更改(例如 up/down 箭头键或 Tab 键)。你想做的就是响应一个项目点击并获取点击的项目,对吧?为此,您可以 x:Bind 将 ItemClick 事件发送到视图模型中的方法:
<GridView ItemClick="{x:Bind ViewModel.ItemClick}" SelectionMode="None" IsItemClickEnabled="True">
public void ItemClick(object sender, ItemClickEventArgs e)
{
var item = e.ClickedItem;
}
如果您对视图模型中的 ItemClick 方法签名感到不安,那么您可以创建自己的 ItemClick 行为来执行视图模型中公开的命令,并将命令的参数绑定到被单击的项目。
如果您出于某种原因不使用行为,那么您可以制作自己的附件 属性,如下所示:
public class ViewHelpers
{
#region ItemClickCommand
public static readonly DependencyProperty ItemClickCommandProperty =
DependencyProperty.RegisterAttached("ItemClickCommand", typeof(ICommand), typeof(ViewHelpers), new PropertyMetadata(null, onItemClickCommandPropertyChanged));
public static void SetItemClickCommand(DependencyObject d, ICommand value)
{
d.SetValue(ItemClickCommandProperty, value);
}
public static ICommand GetItemClickCommand(DependencyObject d)
{
return (ICommand)d.GetValue(ItemClickCommandProperty);
}
static void onItemClickCommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listView = d as ListViewBase;
if (listView == null)
throw new Exception("Dependency object must be a ListViewBase");
listView.ItemClick -= onItemClick;
listView.ItemClick += onItemClick;
}
static void onItemClick(object sender, ItemClickEventArgs e)
{
var listView = sender as ListViewBase;
var command = GetItemClickCommand(listView);
if (command != null && command.CanExecute(e.ClickedItem))
command.Execute(e.ClickedItem);
}
#endregion
}
XAML 不需要使用 MVVM 模式,这意味着您需要自己编写很多 "missing" 功能来使 MVVM 对您来说更容易(如上面的 ItemClick 附件属性)。也许模板 10 已经为您提供了一些行为?我不熟悉。