C# WPF 与用户控件的双向绑定不起作用

问题描述 投票:0回答:1

我创建了一个用户控件,我想将对控件所做的更新反映到 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 来使集合通用,但是双向绑定是有效的。

c# wpf wpf-controls
1个回答
0
投票

正如我帖子底部提到的,问题不是绑定,而是尝试转换类型时出错。 从

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));
© www.soinside.com 2019 - 2024. All rights reserved.