删除选择时选择的项目会从列表框中
删除我有一个ListBox
有其ItemsSource
绑定到(正确)实现了一个INotifyCollectionChanged
并在视图模型绑定到一个领域SelectedItem
自定义类。删除选择时选择的项目会从列表框中
问题是,当我从ItemsSource
集合中删除当前SelectedItem
时,它立即将选择更改为相邻项目。我非常希望如果它只是删除选择。
对我来说这是一个问题的原因如下。 ItemsSource
类包含来自某些其他集合的元素,它们可以满足一些(在运行时常量期间)Predicate或Active
。正在Active
与SelectedItem
“同步”(这是有原因的)。因此,只有在选择了某个项目时,才有可能在ListBox
中允许该项目,这意味着当用户选择其他项目时,该项目可能会消失。
我的功能(深“模型”)时SelectedItem
得到改变被调用:
//Gets old Active item
var oldActiveSchema = Schemas.FirstOrDefault(sch => sch.IsActive);
//Makes the new Item active (which triggers adding it into `ItemsSource` in case it didn't satisfy the Predicate)
((PowerSchema)newActiveSchema).IsActive = true;
//Triggers PropertyChanged on ViewModel with the new Active item
CurrentSchema = newActiveSchema;
RaisePropertyChangedEvent(nameof(CurrentSchema)); (#1)
//Changes the old item so it stops being Active -> gets removed from `ItemsSource` (#2)
if (oldActiveSchema != null) { ((PowerSchema)oldActiveSchema).IsActive = false; }
的问题是,由于某种原因ListBox
的更新,由于这是应该被触发的SelectedItem
变化(#1)推迟(消息更新ListBox
可能最终在一个WPF消息循环,并在那里等待,直到当前计算完成)。
的oldActiveSchema
从ItemsSource
去除,而另一方面,是即时的,也即时启动SelectedItem
一个一个的旁边旧的变化(当您删除选定的项目,一个邻居被选中代替) 。并且由于SelectedItem
的更改触发了我的功能,它将CurrentSchema
设置为错误(相邻)项目,它会重写用户选择的CurrentSchema
(#1),并且在更新ListBox
由于PropertyChanged
得到运行时,它只会更新它相邻的一个。
任何帮助,非常感谢。
实际的代码,如果有人想深入了解:
- ListBox
- ViewModel
- The model's method
-
Callstack当相邻项目被选为
SelectedItem
而不是一个用户选择- 线46:由用户选择的
SelectedItem
进入方法为一体,是应该得到活性 - 线45:旧
SelectedItem
停止活跃 - >被从集合中移除(44-41) - 线32:
MoveCurrencyOffDeletedElement
移动SelectedItem
- 线5:
SelectedItem
得到改变到相邻的一个
- 线46:由用户选择的
您问题的关键在于您在ListBox
上设置了IsSynchronizedWithCurrentItem="True"
。它的作用是保持ListBox.SelectedItem
和ListBox.Items.CurrentItem
同步。另外,ListBox.Items.CurrentItem
与源集合的默认集合视图的ICollectionView.CurrentItem
属性同步(在您的情况下,此视图由CollectionViewSource.GetDefaultView(Schemas)
返回)。现在,当您从Schemas
集合中删除一个项目(也恰好是相应集合视图的CurrentItem
)时,默认情况下视图将其CurrentItem
更新为下一个项目(或前一个,如果删除的项目是最后一个,或者如果删除的项目是集合中唯一的项目,则为null
)。
问题的第二部分是,当ListBox.SelectedItem
改变引起的更新您的视图模型属性,您的RaisePropertyChangedEvent(nameof(ActiveSchema))
处理后的更新过程结束时,特别是从ActiveSchema
返回的控制之后二传手。您可以观察到吸气剂不会立即被击中,而只会在吸气器完成后才会被击中。重要的是,Schemas
视图的CurrentItem
也不会立即更新以反映新选择的项目。另一方面,当您在先前选择的项目上设置IsActive = false
时,会立即从Schemas
集合中“移除”该项目,这会导致集合视图的CurrentItem
更新,并且链条会立即继续更新ListBox.SelectedItem
。你可以观察到在这一点上,ActiveSchema
二传手会再次被击中。因此即使在您完成前一个更改(对用户选择的项目)的处理之前,您的ActiveSchema
也会再次更改(到之前所选项目旁边的项目)。
解决方案
有解决这一问题的几种方法:
#1
设置IsSynchronizedWithCurrentItem="False"
您ListBox
(或离开它不变)。这会让你的问题毫不费力地消失。如果出于某种原因需要使用其他解决方案。
#2
防止折返尝试通过使用保护标志设置ActiveSchema
:
bool ignoreActiveSchemaChanges = false;
public IPowerSchema ActiveSchema
{
get { return pwrManager.CurrentSchema; }
set
{
if (ignoreActiveSchemaChanges) return;
if (value != null && !value.IsActive)
{
ignoreActiveSchemaChanges = true;
pwrManager.SetPowerSchema(value);
ignoreActiveSchemaChanges = false;
}
}
}
这将导致自动更新集合视图的CurrentItem
由您的视图模型被忽略,最终ActiveSchema
将维持预期值。
#3
手动更新集合视图的CurrentItem
到新选择的项目,然后“删除”之前选择的一个。您需要参考MainWindowViewModel.Schemas
集合,因此您可以将其作为参数传递给setNewCurrSchema
方法,或将代码封装在委托中并将其作为参数传递。我将只显示第二个选项:
在PowerManager
类:
//we pass the action as an optional parameter so that we don't need to update
//other code that uses this method
private void setNewCurrSchema(IPowerSchema newActiveSchema, Action action = null)
{
var oldActiveSchema = Schemas.FirstOrDefault(sch => sch.IsActive);
((PowerSchema)newActiveSchema).IsActive = true;
CurrentSchema = newActiveSchema;
RaisePropertyChangedEvent(nameof(CurrentSchema));
action?.Invoke();
if (oldActiveSchema != null)
{
((PowerSchema)oldActiveSchema).IsActive = false;
}
}
在MainWindowViewModel
类:
public IPowerSchema ActiveSchema
{
get { return pwrManager.CurrentSchema; }
set
{
if (value != null && !value.IsActive)
{
var action = new Action(() =>
{
//this will cause a reentrant attempt to set the ActiveSchema,
//but it will be ignored because at this point value.IsActive == true
CollectionViewSource.GetDefaultView(Schemas).MoveCurrentTo(value);
});
pwrManager.SetPowerSchema(value, action);
}
}
}
但请注意,这需要对PresentationFramework
集的引用。如果您不希望视图模型程序集中存在该依赖关系,则可以创建一个将由视图订阅的事件,并且所需的代码将由视图运行(这已取决于程序集PresentationFramework
)。该方法通常被称为交互请求模式(参见User Interaction Patterns)Prism 5.0指南MSDN)。
#4
推迟先前选择的项目的“去除”,直到该绑定更新结束。这可以通过排队的代码来实现,以使用执行的Dispatcher
:
private void setNewCurrSchema(IPowerSchema newActiveSchema)
{
var oldActiveSchema = Schemas.FirstOrDefault(sch => sch.IsActive);
((PowerSchema)newActiveSchema).IsActive = true;
CurrentSchema = newActiveSchema;
RaisePropertyChangedEvent(nameof(CurrentSchema));
if (oldActiveSchema != null)
{
//queue the code for execution
//in case this code is called due to binding update the current dispatcher will be
//the one associated with UI thread so everything should work as expected
Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
((PowerSchema)oldActiveSchema).IsActive = false;
});
}
}
这需要参照WindowsBase
组件,其又可以在视图模型组件利用为溶液#3中所述的方法来避免。
就我个人而言,我会选择解决方案#1或#2,因为它可以让您的PowerManager
类保持清洁,并且#3和#4似乎很容易出现意想不到的行为。
谢谢,我有类似于#3和#4的想法,但这两种解决方案在我看来似乎有点“脏”。另外,我真的不想用UI相关的黑客来污染'PowerManager'。 #2是一个好主意。尽管它很明显,但我没有想到那个。不过,我会和#1一起去。我有点误解了IsSynchronizedWithCurrentItem ='做了什么,并认为这是我的用例需要的。事实并非如此。 – Petrroll
删除选择应该只需要将ViewModel中的SelectedItem属性设置为null。由于问题是模式更改,只需将您的selecteditem存储在本地变量中,将selecteditem设置为null,然后*然后*从集合中删除所选项目。 –
放在[#WPF](https://chat.*.com/rooms/18165/wpf)中,如果我没有在聊天中激活,请给我一个ping。我查看了您的代码,但无法立即找出如何触发该问题。还有其他很多有帮助的居民,如果我不活跃,他们可以伸出援助之手。 – Maverik
布兰登的选择改变的想法也是我的想法。另外,如果您将Selector.IsSelected绑定到IsActive ..您的选择可以自动跟随IsActive标志,而不必处理当前项目 – Maverik