简短版本:
我遇到了ListBox容器的IsSelected
属性和ListBox项的双向绑定问题,这会在更改项目的IsSelected
属性时导致datatemplated项的外观出现意外行为在我的ViewModel中。我正在寻求帮助,因为我不明白问题所在。
长版: 我正在使用ListBox创建CustomControl。我正在使用DataTemplate来设置ListBox中对象的样式。
的DataTemplate :
<DataTemplate DataType="{x:Type substratePresenter:Target}">
<Ellipse Fill="{Binding MyColor}"
Width="{Binding Source={StaticResource WellSize}}"
Height="{Binding Source={StaticResource WellSize}}"
StrokeThickness="1.5"
Canvas.Left="{Binding Path=XPos}"
Canvas.Top="{Binding Path=YPos}"
ToolTip="{Binding Name}"
SnapsToDevicePixels="True"
Cursor="Hand">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding Path=MouseEnterCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<i:InvokeCommandAction Command="{Binding Path=MouseLeaveCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Ellipse.Effect>
<!--THIS IS HACK SO THAT THE INITIAL STATE OF THE HOVEROVER SHADOW IS "OFF"-->
<DropShadowEffect Color="Blue" BlurRadius="10" ShadowDepth="0" Opacity="0" />
</Ellipse.Effect>
<Ellipse.Style>
<Style TargetType="Ellipse">
<Style.Resources>
<!-- REF for using Storyboard animation, Glowon: http://stackoverflow.com/questions/1425380/how-to-animate-opacity-of-a-dropshadoweffect -->
<Storyboard x:Key="GlowOn">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(Effect).Opacity">
<SplineDoubleKeyFrame KeyTime="0:0:0.0" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="GlowOff">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(Effect).Opacity">
<SplineDoubleKeyFrame KeyTime="0:0:0.0" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Style.Resources>
<Setter Property="Effect">
<Setter.Value>
<DropShadowEffect Color="Blue" BlurRadius="10" ShadowDepth="0" Opacity=".75" />
</Setter.Value>
</Setter>
<Setter Property="Stroke" Value="Black"/>
<Style.Triggers>
<!--Handel target target selection-->
<DataTrigger Binding="{Binding Path=IsSelected}" Value="True">
<Setter Property="Stroke" Value="White"/>
</DataTrigger>
<!--Handel target hovering-->
<!-- REF for using DataTrigger: https://msdn.microsoft.com/de-de/library/aa970679%28v=vs.90%29.aspx -->
<DataTrigger Binding="{Binding IsGroupHovered}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource GlowOn}"/>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource GlowOff}"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
</DataTemplate>
如上所示,当项IsSelected
为IsSelected
时,我使用true
属性将笔画的颜色从黑色更改为白色。要选择一个项目并相应地更改其外观,我将IsSelected
中的ItemContainerStyle
属性绑定到我的datatemplated项目的IsSelected
属性。
ListBox XAML :
<ListBox
x:Name="TargetListBox"
BorderThickness="0"
Width="{StaticResource LayoutGridWidthColumn1}"
Height="{StaticResource LayoutGridHeightRow1}"
ItemsSource="{Binding Path=TargetCollection}"
SelectionMode="Extended"
Grid.Column="1" Grid.Row="1"
Background="Transparent"
>
<i:Interaction.Behaviors>
<behavior:RubberBandBehavior />
</i:Interaction.Behaviors>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" Background="Transparent"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<EventSetter Event="MouseDoubleClick" Handler="listBoxItem_DoubleClick" />
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Canvas.Left"
Value="{Binding XPos, Converter={StaticResource horizontalValueConverter},
ConverterParameter={StaticResource substrateWidth}}"/>
<Setter Property="Canvas.Top"
Value="{Binding YPos, Converter={StaticResource verticalValueConverter},
ConverterParameter={StaticResource substrateHeight}}"/>
<!--Bind IsSelected property of ListBoxItem to that of the Target-->
<!--REF: http://stackoverflow.com/questions/1875450/binding-the-isselected-property-of-listboxitem-to-a-property-on-the-object-from-->
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
<!--<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=OneWayToSource}"/>-->
<!--Hide the background-highlighting of the ListBox-Selection, since we handle this from the Items-->
<!--REF: http://stackoverflow.com/questions/2138200/change-background-color-for-selected-listbox-item-->
</Style>
</ListBox.ItemContainerStyle>
<!--REF: http://stackoverflow.com/questions/4343793/how-to-disable-highlighting-on-listbox-but-keep-selection-->
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent" />
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Black" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}" Color="Transparent" />
</ListBox.Resources>
</ListBox>
我现在正尝试实施双击行为以选择相同项目的组。我的代码中有这个双击事件方法:
void listBoxItem_DoubleClick(object sender, MouseButtonEventArgs e)
{
(((ListBoxItem)sender).Content as Target).MouseSelectGroupCommand.Execute(null);
}
MouseSelectGroupCommand
的{{1}}命令在ObservableCollection Target
中找到该组的另一个Targets
,它与所选的一组相同并设置其TargetCollection
}属性IsSelected
。
我现在遇到的问题是,当我对目标执行双击时,只有该目标会更改其笔触颜色,而不会更改该组的其他目标。
要尝试调试,我已完成以下操作:
1)确认组中所有目标的true
属性确实设置为IsSelected
,这种情况。
2)我已将true
中的约束从<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=TwoWay}"/>
更改为<Setter Property="IsSelected" Value="{Binding Path=IsSelected, Mode=OneWayToSource}/>"
。当我这样做时,它会工作,并且整个组的笔触颜色会按预期变化。但是我丢失了ListBox的选择行为,然后我必须重新实现(例如取消选择,选择另一个项目等)。因此,我想避免这种情况。
此外,我正在使用完全相同的方法来更改整个组的<ListBox.ItemContainerStyle>
,当该组的成员目标正在悬停时(请参阅DataTemplate),在这种情况下,它完全正常。
因此,我得出结论,它与DropShadowEffect
属性的绑定有某种关系。如果有任何解决方法,我将不胜感激。
更新
以下是IsSelected
执行的代码。它使用MvvmLight Messenger将消息发送到其包含的集合,该集合查找其他相同的目标并将其MouseSelectGroupCommand
属性设置为IsSelected
。我知道它根本不漂亮,但我仍然是WPF的新手,这是我工作的ATM。我很想听听如何更好地处理这个问题的建议,尽管这完全是不同的问题。
MouseSelectGroupCommand :
true
SelectGroup 命令,在收到public RelayCommand MouseSelectGroupCommand { get; set; }
private void ExecuteSelectTargetGroup()
{
List<Target> selectedTarget = new List<Target>();
selectedTarget.Add(this);
Messenger.Default.Send(new SelectTargetGroup(selectedTarget));
}
消息时,在包含目标的ObservableCollection中执行:
SelectTargetGroup
这就是我在ObservableCollection的构造函数中设置命令的方法:
public void SelectGroup(IList<Target> selectedTarget)
{
IList<Target> targetGroup = GetTargetsWithSameActions(selectedTarget[0]);
SetGroupSelected(targetGroup);
}
public void SetGroupSelected(IList<Target> targetGroup)
{
foreach (Target target in targetGroup)
{
target.PropertyChanged -= TargetPropertyChanged;
target.IsSelected = true;
target.PropertyChanged += TargetPropertyChanged;
}
}
更新:我很清楚问题的根源在于我的草率实施。答案ВасилийШапенко应该帮助我实现更清洁的实施,从而解决问题,这就是我接受它的原因。
答案 0 :(得分:2)
好的,这是一个小解决方案:
首先是背景部分。在下面的代码中,我们创建一个主视图模型,向其添加属性项,并用一堆模型填充它。 OnModelSelectionChanged通过选择模型组来完成工作。
public class MainViewModel
{
private ObservableCollection<SelectionItemViewModel> items;
public MainViewModel()
{
FillItems();
}
private void FillItems()
{
var models=Enumerable.Range(0, 10)
.SelectMany(
index =>
Enumerable.Range(0, 3)
.Select(i => new Model() {Id = index, Name = string.Format("Name_{0}_{1}", index, i)})).Select(
delegate(Model m)
{
var selectionItemViewModel = new SelectionItemViewModel()
{
Value = m
};
selectionItemViewModel.PropertyChanged += OnModelSelectionChanged;
return selectionItemViewModel;
});
Items=new ObservableCollection<SelectionItemViewModel>(models);
}
private void OnModelSelectionChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsSelected")
{
var model = sender as SelectionItemViewModel;
foreach (var m in Items.Where(i=>i.Value.Id==model.Value.Id && model!=i))
{
if (m.IsSelected != model.IsSelected)// This one to prevent infinite loop on selection, on double click there is no need for it
{
m.IsSelected = model.IsSelected;
}
}
}
}
public ObservableCollection<SelectionItemViewModel> Items
{
get { return items; }
set { items = value; }
}
}
public class SelectionItemViewModel:INotifyPropertyChanged
{
private bool isSelected;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public bool IsSelected
{
get { return isSelected; }
set { isSelected = value;
OnPropertyChanged();//For .Net<4.5, use OnPropertyChanged("IsSelected")
}
}
public Model Value { get; set; }
}
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
}
XAML。这是简单的绑定,没有什么复杂的。
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox SelectionMode="Multiple" ItemsSource="{Binding Items}" DisplayMemberPath="Value.Name">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Grid>
</Window>
MainWindow.xaml.cs - 这里我们将ViewModel放入MainWindow DataContext:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
双击支持:
在MainWindow.xaml.cs中:
private void Control_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var source = e.OriginalSource as FrameworkElement;
var mainViewModel = DataContext as MainViewModel;
if (source != null)
{
var model = source.DataContext as SelectionItemViewModel;
model.IsSelected = !model.IsSelected;
if (model != null)
{
foreach (var m in mainViewModel.Items.Where(i => i.Value.Id == model.Value.Id && model != i))
{
if (m.IsSelected != model.IsSelected)
{
m.IsSelected = model.IsSelected;
}
}
}
}
}
在MainWindow.xaml:
<ListBox MouseDoubleClick="Control_OnMouseDoubleClick" SelectionMode="Multiple" ItemsSource="{Binding Items}" DisplayMemberPath="Value.Name">
并在OnModelSelection中注释代码。
这是一种直接的粗略方法。更优雅的方法是创建绑定到双击并附加到ListBoxItem的命令,但这需要更多代码来编写和理解附加属性的概念。
另请参阅MouseButtonEventArgs,它将帮助您确定单击哪个按钮以及按下了哪个控制键。
进一步阅读的关键词:InputBinding,AttachedProperty,ICommand。