我创建了一个用户控件,我想将对控件所做的更新反映到 ViewModel,但我无法让它工作。
我要绑定TwoWay的属性是SearchFilter和SelectedMembers。 编辑:我设法使 SearchFilter 以两种方式工作。问题仅出在我的 ObservableCollection 上。所以以后就不提了
我的组件定义如下:
AutoCompComboBox.xaml.cs
public partial class AutoCompComboBox : UserControl
{
#region SearchFilter
public string SearchFilter
{
get => (string)GetValue(SearchFilterProperty);
set => SetValue(SearchFilterProperty, value);
}
public static readonly DependencyProperty SearchFilterProperty =
DependencyProperty.Register(nameof(SearchFilter), typeof(string), typeof(AutoCompComboBox),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSearchFilterChangedCallBack));
private static void OnSearchFilterChangedCallBack(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is AutoCompComboBox autoComp)
{
autoComp.OnSearchFilterChanged();
}
}
#endregion
#region ItemsSource
public object ItemsSource
{
get => GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(nameof(ItemsSource), typeof(object), typeof(AutoCompComboBox), new PropertyMetadata(0));
#endregion
#region TextPath
public string TextPath
{
get => (string)GetValue(TextPathProperty);
set => SetValue(TextPathProperty, value);
}
public static readonly DependencyProperty TextPathProperty =
DependencyProperty.Register(nameof(TextPath), typeof(string), typeof(AutoCompComboBox), new PropertyMetadata(""));
#endregion
#region DisplayMember
public string DisplayMember
{
get => (string)GetValue(DisplayMemberProperty);
set => SetValue(DisplayMemberProperty, value);
}
public static readonly DependencyProperty DisplayMemberProperty =
DependencyProperty.Register(nameof(DisplayMember), typeof(string), typeof(AutoCompComboBox), new PropertyMetadata(""));
#endregion
#region SelectedMembers
public ObservableCollection<object> SelectedMembers
{
get => (ObservableCollection<object>)GetValue(SelectedMembersProperty);
set => SetValue(SelectedMembersProperty, value);
}
public static readonly DependencyProperty SelectedMembersProperty =
DependencyProperty.Register(nameof(SelectedMembers), typeof(ObservableCollection<object>), typeof(AutoCompComboBox),
new FrameworkPropertyMetadata(new ObservableCollection<object>(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
#endregion
private ObservableCollection<object> ObjCollection;
private List<object> LstObject;
public AutoCompComboBox()
{
InitializeComponent();
}
protected virtual void OnSearchFilterChanged()
{
if(LstObject != null)
{
ObjCollection = new ObservableCollection<object>();
foreach (object obj in LstObject)
{
string number = obj.GetType().GetProperty(TextPath).GetValue(obj, null).ToString().ToLower();
if (number.Contains(SearchFilter.ToLower()))
{
ObjCollection.Add(obj);
}
}
ItemsSource = ObjCollection;
}
}
private void ComboBoxItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 1 && e.ChangedButton == MouseButton.Left)
{
ContentPresenter contentPresenter = sender as ContentPresenter;
if (!SelectedMembers.Contains(contentPresenter.DataContext))
{
SelectedMembers.Add(contentPresenter.DataContext);
}
}
}
private void ToggleButton_Checked(object sender, RoutedEventArgs e)
{
LstObject = (ItemsSource as IEnumerable<object>).Cast<object>().ToList();
if (SearchFilter == null)
{
SearchFilter = "";
}
OnSearchFilterChanged();
}
private void ToggleButton_Unchecked(object sender, RoutedEventArgs e)
{
ItemsSource = new ObservableCollection<object>(LstObject);
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
object value = button.DataContext;
SelectedMembers.Remove(value);
}
}
AutoCompComboBox.xaml
<UserControl x:Class="Project.Components.GenericComponents.AutoCompComboBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Project.Components.GenericComponents"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
x:Name="AutoCompleteControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding SelectedMembers, ElementName=AutoCompleteControl, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
DisplayMemberPath="{Binding DisplayMember, ElementName=AutoCompleteControl, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Height="50"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<!--#region ListBox item style-->
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border Background="#D5E2FB"
BorderThickness="1"
BorderBrush="#D5E2FB"
CornerRadius="10"
Margin="1"
Height="22"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<DockPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="8,0,5,0"/>
<!--#region Close item button style-->
<Button Content="🗙"
Click="CloseButton_Click">
<Button.Style>
<Style TargetType="Button">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Height" Value="20" />
<Setter Property="Width" Value="20" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="#2B2B2B" />
<Setter Property="FontWeight" Value="8"/>
<Setter Property="Content" Value="🗙" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid Background="{TemplateBinding Background}">
<Ellipse x:Name="ButtonBackground"
Width="20"
Height="20"/>
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
SnapsToDevicePixels="True">
</ContentPresenter>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="ButtonBackground" Property="Fill" Value="#F2F5F8" />
<Setter Property="Cursor" Value="Hand" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
<!--#endregion-->
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
<!--#endregion-->
</ListBox>
<ComboBox x:Name="AutoCompCB"
Grid.Row="1"
IsEditable="True"
Tag="False"
ItemsSource="{Binding ItemsSource, ElementName=AutoCompleteControl, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
DisplayMemberPath="{Binding DisplayMember, ElementName=AutoCompleteControl, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<ComboBox.Resources>
<Style TargetType="{x:Type ComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBox}">
<Grid x:Name="MainGrid"
SnapsToDevicePixels="true">
<Popup x:Name="PART_Popup"
StaysOpen="True"
AllowsTransparency="true"
IsOpen="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}"
Margin="1"
PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}"
Placement="Bottom"
Width="{Binding Path=ActualWidth, ElementName=SearchField}">
<Border x:Name="DropDownBorder"
BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"
BorderThickness="1"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
<ScrollViewer x:Name="DropDownScrollViewer">
<Grid RenderOptions.ClearTypeHint="Enabled">
<Canvas HorizontalAlignment="Left"
Height="0"
VerticalAlignment="Top"
Width="0">
<Rectangle x:Name="OpaqueRect"
Height="{Binding ActualHeight, ElementName=DropDownBorder}"
Width="{Binding ActualWidth, ElementName=DropDownBorder}"/>
</Canvas>
<ItemsPresenter x:Name="ItemsPresenter"
KeyboardNavigation.DirectionalNavigation="Contained"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Grid>
</ScrollViewer>
</Border>
</Popup>
<Border x:Name="SearchField">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<!--<TextBox Text="{Binding SearchFilter, ElementName=AutoCompleteControl, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>-->
<TextBox Text="{Binding SearchFilter, ElementName=AutoCompleteControl, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
<!--#region Style du bouton pour ouvrir/fermer la popup -->
<ToggleButton Width="25"
HorizontalAlignment="Right"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
IsChecked="{Binding Tag, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
Checked="ToggleButton_Checked"
Unchecked="ToggleButton_Unchecked">
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Content">
<Setter.Value>
<Border Background="Transparent"
BorderBrush="Transparent"
BorderThickness="1">
<Path Width="15"
Height="15"
Fill="#2B2B2B"
Stretch="Uniform"
Data="M903.232 256l56.768 50.432L512 768 64 306.432 120.768 256 512 659.072z" />
</Border>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Content">
<Setter.Value>
<Border Background="Transparent"
BorderBrush="Transparent">
<Path Width="15"
Height="15"
Fill="#2B2B2B"
Stretch="Uniform"
Data="M903.232 768l56.768-50.432L512 256l-448 461.568 56.768 50.432L512 364.928z"/>
</Border>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
</ToggleButton>
<!-- #endregion -->
</Grid>
</Border>
<ContentPresenter ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
Content="{TemplateBinding SelectionBoxItem}"
ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
IsHitTestVisible="false"
Margin="{TemplateBinding Padding}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasItems" Value="false">
<Setter Property="Height" TargetName="DropDownBorder" Value="95"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
<Setter Property="Background" Value="#FFF4F4F4"/>
</Trigger>
<Trigger Property="IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</Trigger>
<Trigger Property="ScrollViewer.CanContentScroll" SourceName="DropDownScrollViewer" Value="false">
<Setter Property="Canvas.Top" TargetName="OpaqueRect" Value="{Binding VerticalOffset, ElementName=DropDownScrollViewer}"/>
<Setter Property="Canvas.Left" TargetName="OpaqueRect" Value="{Binding HorizontalOffset, ElementName=DropDownScrollViewer}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<ContentPresenter PreviewMouseDown="ComboBoxItem_PreviewMouseDown"/>
<!--<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True" SourceName="ItemBorder">
<Setter Property="Background" Value="#99ccff" TargetName="ItemBorder"/>
<Setter Property="Opacity" Value="0.6"/>
</Trigger>
</ControlTemplate.Triggers>-->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.Resources>
</ComboBox>
</Grid>
</UserControl>
UserControl 在我的 HomePage.xaml 中使用,如下所示:
<GenericComps:AutoCompComboBox SearchFilter="{Binding UserFavorites.StringSearch}"
ItemsSource="{Binding UserFavorites.CollectionMembers}"
SelectedMembers="{Binding UserFavorites.CollectionSelectedMembers}"
TextPath="Numero"
DisplayMember="Numero"/>
在我的 HomePageVM.cs 中,我定义了:
public class HomePageVM : BaseVM
{
public Utilisateur Utilisateur { get; set; }
public Favoris UserFavorites { get; set; }
public HomePageVM(Window ctxtWindow)
{
Utilisateur = BD.GetUser(Environment.UserName);
UserFavorites = new Favoris(Utilisateur);
UserFavorites.GetUserFavoris();
}
Favoris.cs 类包含:
public class Favoris : INotifyPropertyChanged
{
public Utilisateur Utilisateur { get; set; }
public ObservableCollection<ColFavori> CollectionMembers { get; private set; }
private string _stringSearch;
public string StringSearch
{
get => _stringSearch;
set
{
_stringSearch = value;
OnPropertyChanged(nameof(StringSearch));
}
}
public ObservableCollection<ColFavori> CollectionSelectedMembers { get; set; }
public Favoris(Utilisateur utilisateur)
{
Utilisateur = utilisateur;
StringSearch = "";
CollectionSelectedMembers = new ObservableCollection<ColFavori>();
}
public GetUserFavoris()
{
DataTable ColTable = BD.GetColFav(Utilisateur);
CollectionMembers = new ObservableCollection<ColFavori>(CommonMethods.ConvertToList<ColFavori>(ColTable));
//CommonMethods.ConvertToList creates a List given the DataTable
}
ColFavori 类是这样定义的。
public class Colfavori
{
public long ID { get; set; }
public string Numero { get; set; }
public string Designation { get; set; }
}
在显示 CollectionMembers 中包含的数据时,将我的数据发送到用户控件没有任何问题。 当我通过从 ComboBox 单击它来添加一个对象(此处为 ColFavori 以供参考)时,它被添加到 SelectedMembers 中并在 ListBox 中可见。但是我从(CollectionSelectedMembers)绑定的SelectedMembers项没有更新。
我尝试遵循 StackOverflow 的各种答案,包括使用
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault
,使用 Binding UpdateSourceTrigger
和 Binding Mode
进行调整,但无法使任何工作正常进行。
编辑: 我尝试将
ObservableCollection<object>
更改为 ObservableCollection<ColFavori>
并且两种绑定方式都有效。我在查看 Visual Studio 上的 Xaml 绑定失败选项卡时发现了这一点,发现我有一个转换错误。
所以我需要找到 ho 来使集合通用,但是双向绑定是有效的。
正如我帖子底部提到的,问题不是绑定,而是尝试转换类型时出错。 从
ObservableCollection<PNFavori>
到 ObservableCollection<object>
的转换不起作用并导致绑定错误。
为了使我的列表通用且不破坏绑定仍然有效,我使用了
IList
。它允许我继续对列表进行操作(如添加、删除、迭代)并拥有一个有效的通用绑定。
这是更改的代码:
public IList SelectedMembers
{
get => (IList)GetValue(SelectedMembersProperty);
set => SetValue(SelectedMembersProperty, value);
}
public static readonly DependencyProperty SelectedMembersProperty =
DependencyProperty.Register(nameof(SelectedMembers), typeof(IList), typeof(AutoCompComboBox),
new FrameworkPropertyMetadata(new List<object>(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));