在WPF C#中创建一个自定义控件,该控件结合了多个控件的功能组合。

3
我希望创建一个自定义控件,它应该是预定义控件的组合,例如文本框、按钮、列表框等。
请参考以下控件(仅为示例):
<Grid.RowDefinitions>
    <RowDefinition Height="30" />
    <RowDefinition Height="50" />
</Grid.RowDefinitions>
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="300" />
        <ColumnDefinition Width="100" />
    </Grid.ColumnDefinitions>
    <TextBox Grid.Column="0" />
    <Button Grid.Column="1" Content="Add" Margin="20,0" />
</Grid>
<ListBox ItemsSource="{Binding textBox}" Grid.Row="1" Margin="0,25">
    <ListBoxItem />
</ListBox>
</Grid>

我需要一个包含多个控件的自定义控件。当我点击按钮时,我需要将文本框中的值添加到列表项中,并最终获取该控件的列表。

期望的自定义控件(仅供参考)

<cust:MultiControl ItemsSource="{Binding stringCollection}" />

描述:

我需要从用户那里获取字符串列表。我添加了一个文本框来获取用户的输入。我添加了一个按钮将文本添加到 List<string> 中。为了显示列表,我添加了一个 ListBox。

我需要一个单一的控件,它应该继承这三个控件。在其中,我需要一个双向绑定ItemsSource。如果 List<string> 更新了,它应该更新 ItemSource。

我在15个以上的地方都使用了这个结构。因此,我希望将其制作成自定义控件。请指导我如何将其实现为单一控件?

enter image description here

我不需要用户控件,我需要自定义控件,请帮助我……

即使 ItemsSource 有值,Item Source ViewModel 集合也没有更新。

enter image description here


CustomControl只是从Control派生的类。UserControl也是从Control(间接)派生的类,因此UserControl是CustomControl。UserControl很酷的一点是你可以使用XAML文件+代码轻松定义它们,而CustomControl只是代码。所以你真的想要一个UserControl。你只需要添加自定义代码,比如DependencyProperties(用于公开的ItemSource)和自定义逻辑。 - Simon Mourier
你基本上需要一个工作的自定义控件示例吗?如何从头开始构建一个? - Mauro Sampietro
请告诉我们,为什么您不只是制作一个CustomControl?您的问题是什么?如果您知道如何编写UserControl,那么编写CustomControl应该没有问题...我真的不明白您在问什么。 - lokusking
@lokusking,我需要一个具有我在问题中提到的结构的控件。其中,我需要一个ItemSource属性来绑定List<string>。该控件应该处理我在问题中提到的所有功能。如果您在用户控件中有解决方案,请给出您的答案。 - B.Balamanigandan
2个回答

15
我已经为您创建了一个所需的自定义控件的最简示例。 控件
public class MyCustomControl : ItemsControl {

        static MyCustomControl() {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl), new FrameworkPropertyMetadata(typeof(MyCustomControl)));
        }

        private Button _addButton;
        private TextBox _textBox;
        private ListView _itemsView;

        public override void OnApplyTemplate() {
            this._addButton = this.GetTemplateChild("PART_AddButton") as Button;
            this._textBox = this.GetTemplateChild("PART_TextBox") as TextBox;
            this._itemsView = this.GetTemplateChild("PART_ListBox") as ListView;

            this._addButton.Click += (sender, args) => {
                (this.ItemsSource as IList<string>).Add(this._textBox.Text);
            };
            this._itemsView.ItemsSource = this.ItemsSource;
            base.OnApplyTemplate();
        }

        public ICommand DeleteCommand => new RelayCommand(x => { (this.ItemsSource as IList<string>).Remove((string)x); });
    }

模板

 <Style TargetType="{x:Type local:MyCustomControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:MyCustomControl}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="Auto" />
                        </Grid.RowDefinitions>
                        <Grid>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="300" />
                                <ColumnDefinition Width="100" />
                            </Grid.ColumnDefinitions>
                            <TextBox x:Name="PART_TextBox" Grid.Column="0" />
                            <Button x:Name="PART_AddButton" Grid.Column="1" Content="Add" Margin="20,0" />
                        </Grid>
                        <ListView ItemsSource="{TemplateBinding ItemsSource}" Grid.Row="1" Margin="0,25" x:Name="PART_ListBox" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
                            <ListView.ItemTemplate>
                                <DataTemplate>
                                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
                                        <TextBlock Text="{Binding}"/>
                                        <Button Content="X" Foreground="Red" 
                                                Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MyCustomControl}}, Path=DeleteCommand}" 
                                                CommandParameter="{Binding}" Margin="10,0,0,0"></Button>
                                    </StackPanel>
                                </DataTemplate>
                            </ListView.ItemTemplate>
                        </ListView>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

RelayCommand

public class RelayCommand : ICommand
    {
        #region Fields

        private readonly Action<object> _execute;
        private readonly Predicate<object> _canExecute;

        #endregion // Fields

        #region Constructors

        public RelayCommand(Action<object> execute, Predicate<object> canExecute = null)
        {
            if (execute == null)
                throw new ArgumentNullException(nameof(execute));

            this._execute = execute;
            this._canExecute = canExecute;
        }

        #endregion // Constructors

        #region ICommand Members

        [DebuggerStepThrough]
        public bool CanExecute(object parameter)
        {
            return this._canExecute == null || this._canExecute(parameter);
        }

        public event EventHandler CanExecuteChanged {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute(object parameter)
        {
            this._execute(parameter);
        }

        #endregion // ICommand Members
    }

使用方法

 <local:MyCustomControl ItemsSource="{Binding Collection}"/>

注意:不要使用List作为您的ItemsSource。相反,使用ObservableCollection,因为它会自动通知视图,您不必处理更新相关的内容。

干杯!


非常感谢……这太棒了,正是我期望的。感谢您的指导。 - B.Balamanigandan
@IRPunch 更新了带有删除按钮的控件。 - lokusking
@lokusking - 谢谢。但是我需要在<ListView.ItemTemplate>中添加一个删除按钮,以便在每个特定项目中都有一个删除按钮来删除该项目。 - user6060080
@lokusking - 我尝试了一个具有相同逻辑的自动完成文本框。我添加了一个额外的依赖属性IEnumerable<dynamiic> SuggestionItemSource。我将该属性绑定到了列表框中。如果我绑定一个ObservableCollection<string>,那么它可以完美地工作,但如果我使用ObservableCollection<int>,则无法正常工作。 - B.Balamanigandan
@lokusking - 请参考我在底部更新的新截图所提出的问题。当我从ItemSource中添加/删除内容时,如何通知集合已更改。 - B.Balamanigandan
显示剩余6条评论

2
假设这是您的自定义控件:
<UserControl x:Class="CustomControl.UserControl1"
         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:CustomControl"
         mc:Ignorable="d" >
<StackPanel Width="200" Margin="15">
    <TextBox  Name="txtbox"/>
    <Button  Content="Add"
            Margin="20,0"  Click="Button_Click"/>

    <ListBox ItemsSource="{Binding}"
             Margin="0,25">
    </ListBox>

</StackPanel>

这是您的父窗口调用自定义控件的代码:

<Window x:Class="ParentControl.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:ParentControl"
    mc:Ignorable="d"
    xmlns:customcontrol="clr-namespace:CustomControl;assembly=CustomControl"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <customcontrol:UserControl1 Name="customcontrol"></customcontrol:UserControl1>
</Grid>

你有一个字符串集合,希望用文本框中的文本进行更新,可以像这样操作: 在父窗口中将自定义控件的DataContext设置为字符串集合,如下所示:
        public MainWindow()
    {
        InitializeComponent();

        ObservableCollection<string> stringcollection = new ObservableCollection<string>();
        stringcollection.Add("String 1");
        stringcollection.Add("String 2");
        stringcollection.Add("String 2");
        stringcollection.Add("String 3");
        customcontrol.DataContext = stringcollection;

    }

在您的自定义控件后端逻辑中添加按钮点击事件处理程序,并执行以下操作:

 private void Button_Click(object sender, RoutedEventArgs e)
    {
        var button = sender as Button;
        var list = button.DataContext as ObservableCollection<string>;
        list.Add(this.txtbox.Text.ToString());
    }

确保字符串集合的类型为ObservableCollection,否则您的listbox不会在每次单击添加按钮时更新。

希望这有所帮助。


我期望的解决方案是在自定义控件中而不是用户控件中。感谢您的回复。 - B.Balamanigandan
不是自定义控件。请参考我在问题块中附上的屏幕截图。 - B.Balamanigandan
我需要这个作为一个独立的控件。如果我实现用户控件,那么它将依赖于项目级别(即父窗口级别)。 - B.Balamanigandan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接