【问题标题】:Stuck with simple data binding in XAML卡在 XAML 中的简单数据绑定
【发布时间】:2020-07-09 00:41:50
【问题描述】:

我有我想在 TabControl 的不同选项卡中的几个 ComboBox 控件中使用的字符串列表。字符串列表位于公共 ObservableCollection 中。问题是,我无法在 XAML 文件的 部分中显示此集合。这是一个干净的新应用程序中的代码:

using System;
using System.Collections.ObjectModel;
using System.Windows;

namespace APX_Interface
{
    public partial class MainWindow : Window
    {
         public MainWindow()
        {
            InitializeComponent();
        }       
        
        public ObservableCollection<String> MyStringList; // not initialized yet

        public class NameList : ObservableCollection<String>
        {
            public NameList() : base()
            {
                Add("Willam");
                Add("Isak");
                Add("Victor");
                Add("Jules");
            }
        }
    }
}

这是 XAML:

Window x:Class="APX_Interface.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:APX_Interface"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">

<Window.Resources>
    <local:MyStringList x:Key="Strings"/>
    <local:NameList x:Key="Names"/>
</Window.Resources>
<Grid>
    
</Grid>

当我输入 local: 时,唯一显示为自动完成的是 App。编译代码我得到几个这样的错误:

错误 XML 命名空间“clr-namespace:APX_Interface”中不存在标记“MyStringList”。

错误 XDG0008 名称“MyStringList”在命名空间“clr-namespace:APX_Interface”中不存在。

错误 XLS0414 找不到类型“local:MyStringList”。确认您没有丢失程序集引用并且所有引用的程序集都已构建。 类 NameList 的相同错误

在查看了许多示例以及此处的其他讨论后,我无法确定我缺少什么。

感谢所有建议,很遗憾我们还没有解决这个问题。 我在这里发布了带有建议更改的代码和我的 cmets:

using System;
using System.Collections.ObjectModel;
using System.Windows;

namespace APX_Interface
{
    public  class NameList : ObservableCollection<String>
    // This class is now THE ONLY thing visible in <Window.Resources> 
    // but not any instance of the class.
    {
        public NameList() : base()
        {
        }
    }
      
    public partial class MainWindow : Window
    {
        // This instance of the class is NOT visible in <Window.Resources> ???
        public NameList MyNames { get { return _names;} }

        // nor is this
        public ObservableCollection<String> MyNumbers { get { return _numbers; } }

        // nope
        public NameList _names = null;

        //Neither this one
        public ObservableCollection<String> _numbers = null;

        public MainWindow()
        {
            InitializeComponent();

            // this doesn't make any difference
            DataContext = this;

            _names = new NameList(); 
            _names.Add("fellow");           // populate dynamically

            var _nr = new string[]     // static strings
            {
                "One",
                "Two",
                "etc.",
            };

            _numbers = new ObservableCollection<string>(_nr);
        }
    }
}

【问题讨论】:

  • MyStringList 是您的公共字段。在资源中,您只能定义一个实例类型。口头描述你想要实现的东西。因为我无法从您的代码中理解这一点。
  • > 将 MyStringList 转换为属性。 – @neelesh bodgal 它不会解决任何问题。由于 Guillermo 不需要绑定,而是资源中的实例。
  • 您可以使用 usercontrol 或 customcontrol 创建您想要的控件,以便在您的应用程序中任何需要的地方通用
  • 让我更具体地解释一下这个问题。字符串列表是动态生成的(系统中可用的 COM 端口),其他列表是静态的(COM 参数),几个 ComboBox 控件必须在不同的选项卡中呈现这些列表。我现在正在做的是开始在 String[] 数组中加载端口列表并以编程方式设置项目来源,如下所示: DRCPort.ItemsSource = SerialPortsList;等等。这工作正常,但我想把它变成一个可绑定的列表。

标签: c# wpf


【解决方案1】:

在 XAML 和 Code Behind 中访问集合的几个示例。

第一个示例:静态属性中的集合。

XAML:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <ListBox ItemsSource="{x:Static local:MyValues.NameList}" />
    <StackPanel Grid.Column="1">
        <TextBox x:Name="tbNew"/>
        <Button Content="Add" Click="Button_Click"/>
    </StackPanel>
</Grid>

代码背后:

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        MyValues.NameList.Add(tbNew.Text);
    }

第二个例子:在 XAML 资源中创建一个集合实例。

XAML:

<Window.Resources>
    <local:StringCollectionINCC x:Key="Strings">
        <sys:String>Willam</sys:String>
        <sys:String>Isak</sys:String>
        <sys:String>Victor</sys:String>
        <sys:String>Jules</sys:String>
    </local:StringCollectionINCC>
</Window.Resources>
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <ListBox ItemsSource="{Binding Mode=OneWay, Source={StaticResource Strings}}" />
    <StackPanel Grid.Column="1">
        <TextBox x:Name="tbNew"/>
        <Button Content="Add" Click="Button_Click"/>
    </StackPanel>
</Grid>

代码背后:

    private void Button_Click(object sender, RoutedEventArgs e)
    {

        StringCollectionINCC list = (StringCollectionINCC)Resources["Strings"];

        list.Add(tbNew.Text);
    }

第三个示例(最佳):以 MVVM 模式创建集合。

为了创建一个团队,使用了一个实现 ICommand 的附加类:

/// <summary>Executing Delegate.</summary>
/// <param name="parameter">Command parameter.</param>
public delegate void ExecuteHandler(object parameter);
/// <summary>CanExecuting Delegate.</summary>
/// <param name="parameter">Command parameter.</param>
/// <returns><see langword="true"/> - if command execution is allowed.</returns>
public delegate bool CanExecuteHandler(object parameter);

/// <summary>A class implementing the ICommand interface for creating WPF commands.</summary>
public class RelayCommand : ICommand
{
    private readonly CanExecuteHandler _canExecute = CanExecuteDefault;
    private readonly ExecuteHandler _execute;
    private readonly EventHandler _requerySuggested;

    public event EventHandler CanExecuteChanged;

    /// <summary>The constructor of the command.</summary>
    /// <param name="execute">Command Executable Method.</param>
    /// <param name="canExecute">Team Status Method.</param>
    public RelayCommand(ExecuteHandler execute, CanExecuteHandler canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));

        _canExecute = canExecute;

        _requerySuggested = (o, e) => Invalidate();
        CommandManager.RequerySuggested += _requerySuggested;
    }

    /// <summary>The method of invoking an event about a change in command status.</summary>
    public void Invalidate() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);

    public bool CanExecute(object parameter) => _canExecute == null ? true : _canExecute.Invoke(parameter);

    public void Execute(object parameter) => _execute?.Invoke(parameter);

    /// <summary>Default CanExecute Method/</summary>
    /// <param name="parameter">>Command parameter.</param>
    /// <returns>Is always <see langword="true"/>.</returns>
    public static bool CanExecuteDefault(object parameter) => true;
}

带有集合和命令的 ViewModel:

/// <summary>ViewModel</summary>
public class MainVM
{
    public ObservableCollection<string> NameList { get; } 
        = new ObservableCollection<string>()
        {
            "Willam",
            "Isak",
            "Victor",
            "Jules"

        };

    private RelayCommand _addCommand;
    public RelayCommand AddCommand => _addCommand 
        ?? (_addCommand = new RelayCommand(AddMethod, AddCanMethod));

    /// <summary>A method that checks that a parameter can be cast to
    /// a string and that string is not empty.</summary>
    /// <param name="parameter">Command parameter.</param>
    /// <returns><see langword="true"/> - if the conditions are met.</returns>
    private bool AddCanMethod(object parameter)
        => parameter is string val
        && !string.IsNullOrWhiteSpace(val);

    /// <summary>Method to add a value to a collection.</summary>
    /// <param name="parameter">Valid command parameter.</param>
    private void AddMethod(object parameter)
        => NameList.Add((string) parameter);
}

Locator - 访问所有 ViewModel 或其他数据容器的常用解决方案:

/// <summary>Contains all ViewModel. In this case, only one MainVM.</summary>
public class Locator 
{
    public MainVM MainVM { get; } = new MainVM();
}

XAML App - 这里更方便地创建不同 Windows 中可能需要的资源:

<Application.Resources>
    <local:Locator x:Key="Locator"/>
</Application.Resources>

XAML Windows:在 DataContext 中设置 ViewModel 并绑定元素的属性。

<Window ....
        DataContext="{Binding MainVM, Mode=OneWay, Source={StaticResource Locator}}">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <ListBox ItemsSource="{Binding NameList}" />
        <StackPanel Grid.Column="1">
            <TextBox x:Name="tbNew"/>
            <Button Content="Add"
                    Command="{Binding AddCommand, Mode=OneWay}"
                    CommandParameter="{Binding Text, ElementName=tbNew}"/>
        </StackPanel>
    </Grid>
</Window>

【讨论】:

  • 这些例子很棒。它们清楚地展示了如何使用 MVVM 范式处理从最小静态列表到完整数据库,同时坚持“定义一次使用多次”的原则。我看到的官方文档示例只显示了 XAML,但没有显示背后的代码。感谢所有花时间启发我的贡献者。
【解决方案2】:

如果您需要使用 local:NameList 那么您必须按照上面的答案进行操作。如果您需要使用 MyStringList 属性,则必须执行以下操作:

 public partial class MainWindow : Window
        {
            public ObservableCollection<string> _myStringList=new ObservableCollection<string>(); // not initialized yet
            public ObservableCollection<string> MyStringList
            {
                get
                {
                    return _myStringList;
                }
                set
                {
                    _myStringList = value;
                }
            }
    

        public MainWindow()
        {
            
            InitializeComponent();
            fillCombo();
            DataContext = this;
        }
        public void fillCombo()

        {

            MyStringList.Add("Willam");
            MyStringList.Add("Isak");
            MyStringList.Add("Victor");
            MyStringList.Add("Jules");
        }
    }

在 XML 中:

<ComboBox ItemsSource="{Binding MyStringList}" Name="comboBox1"   Margin="34,56,0,0"
              VerticalAlignment="Top"
              Width="200"/>

注意:您还可以在项目中创建用户控件或自定义控件,然后使用所需元素设计控件。之后,您可以使用创建的控件来控制您想要的任何 XML 页面。

【讨论】:

  • 这也与我开始的地方相似,我做了建议的更改,但我仍然看不到可观察集合的任何公共实例或 MainWindow 中可用于绑定的引用它的属性。数据上下文 = 这个;也没有任何区别。对象的任何实例都必须在类内部,不能在命名空间级别。一旦所需的对象作为 Window.Resource 可见,XML 绑定就很简单了。这就是我卡住的地方。
  • 更正:“对象的任何实例都必须在类中,”除非它是静态对象。
【解决方案3】:

有多种解决方案,但要选择特定的一种,您需要提供任务的更多详细信息。

如果您需要一个简单的字符串集合,那么您不需要创建自己的类。 正如@Clements 所写,最好使用 StringCollection。

如果您需要更改集合的能力(INotifyCollectionChanged 接口),您可以添加自己的类。 但是您需要在命名空间中构建它,而不是将其嵌入到另一个类中。 用值初始化它在 XAML 资源中比在 C# 代码中更好。

namespace APX_Interface
{
    public class StringCollectionINCC : ObservableCollection<string> { }
}

如果需要创建一个可从应用程序中的任何位置访问的全局实例,那么您可以创建一个提供该实例的静态属性。

namespace APX_Interface
{
    public  static class MyValues
    {
        public static StringCollectionINCC NameList { get; }
            = new StringCollectionINCC()
            {
                "Willam",
                "Isak",
                "Victor",
                "Jules"
            };
    }
}

您可以像这样在 XAML 中获取它:

<Window.Resources>
    <local:StringCollectionINCC x:Key="Strings">
        <sys:String>Willam</sys:String>
        <sys:String>Isak</sys:String>
        <sys:String>Victor</sys:String>
        <sys:String>Jules</sys:String>
    </local:StringCollectionINCC>
    <x:Static Member="local:MyValues.NameList" x:Key="Names"/>
</Window.Resources>
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <ListBox ItemsSource="{Binding Mode=OneWay, Source={StaticResource Strings}}" />
    <ListBox Grid.Column="1" ItemsSource="{Binding Mode=OneWay, Source={StaticResource Names}}" />
</Grid>

您也可以立即在没有资源的情况下设置指向静态实例的链接:

    <ListBox Grid.Column="1" ItemsSource="{x:Static local:MyValues.NameList}" />

【讨论】:

  • 这个我也试过了,还是不行。上面显示的 ItemsSource={...} 被接受编辑 XAML 文件,但是编译后我得到:错误 XDG0008 名称“MyValues”在命名空间“clr-namespace:APX_Interface”中不存在。这显然不是真的。此外,这是一种非常复杂的方式来呈现一个简单的绑定列表。必须有一种更简单的机制来使 MainWindow 内的可观察集合可用于绑定。上面的其他建议都没有解决它。这里还有其他东西在起作用。
  • 从您的解释和代码中,我无法得出问题的原因。如果可以,制作一个简化项目来演示问题并将其上传到 GitHub。然后向我发送合作邀请。我会看看这个并尝试帮助解决问题。
  • 我非常感谢您的报价,b
  • 对不起,但我不知道如何在 GitHub 中执行此操作,Clemens 给了我一个实际编译和按预期工作的答案。尽管我认为(基于 Microsoft 示例)必须有一种更简单的方法来让 MainWindow 类中的可观察集合成为 ComboBox 的 ItemsSource 从 XAML 中抛出引用,而不是像我一样在后面的代码中手动分配它现在就做。再次感谢 EldHasp!
【解决方案4】:

您已将 NameList 声明为 MainWindow 类中的嵌套类。把它移开。

除此之外,您不需要在 MainWindow 中声明 MyStringList 字段,并且 NameList 不需要是 ObservableCollection,因为您显然在初始化后从未向集合添加或删除元素。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }       
}

public class NameList : List<String>
{
    public NameList()
    {
        Add("Willam");
        Add("Isak");
        Add("Victor");
        Add("Jules");
    }
}

现在像这样使用它:

<Window.Resources>
    <local:NameList x:Key="Names"/>
</Window.Resources>
...
<ComboBox ItemsSource="{StaticResource Names}" .../>

您可以直接在 XAML 中声明字符串列表资源,而不是在代码中声明 NameList 类。

像这样添加 XAML 命名空间声明:

xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:coll="clr-namespace:System.Collections.Specialized;assembly=System"

还有这个资源:

<coll:StringCollection x:Key="Names">
    <sys:String>Willam</sys:String>
    <sys:String>Isak</sys:String>
    <sys:String>Victor</sys:String>
    <sys:String>Jules</sys:String>
</coll:StringCollection>

编辑:如果您希望能够在后面的代码中操作字符串集合,则声明字符串列表资源是错误的方法。

相反,声明一个具有NameList 属性的视图模型类

public class ViewModel : INotifyPropertyChanged
{
    public ObservableCollection<string> NameList { get; }
        = new ObservableCollection<string>();
}

并将其分配给 MainWindow 的 DataContext

private readonly ViewModel viewModel = new ViewModel();

public MainWindow()
{
    InitializeComponent();

    DataContext = viewModel;

    viewModel.NameList.Add("Willam");
    viewModel.NameList.Add("Isak");
    viewModel.NameList.Add("Victor");
    viewModel.NameList.Add("Jules");
}

在 XAML 中,像这样绑定 ComboBox

<ComboBox ItemsSource="{Binding NameList}" .../>

EDIt2:另一种非常简单的方法可能是静态成员,例如在 MainWindow 类中,如

public partial class MainWindow : Window
{
    ...

    public static List<string> NameList { get; } = new List<string>
    {
        "Willam", "Isak", "Victor", "Jules"
    };
}

会这样使用:

<ComboBox ItemsSource="{x:Static local:MainWindow.NameList}" .../>

【讨论】:

  • 该代码类似于我从 MS doc 示例开始的代码。我将课程移出 MainWindow 课程。它现在是可见的,但不是它的任何实例。
  • 一些字符串列表必须动态填充,因此,我需要在运行时在 MainWindow 类中提供一个可观察集合的实例以进行绑定。同意一个类对于一个简单的列表来说太过分了,只需从一个 MS 示例中借用它进行测试。将它移到 MainWindow 类之外使其可见,但不是它的任何实例(有什么意义?),声明 DataContext = this;在 MainWindow 构造函数中也没有任何区别。
  • 那你选择了错误的方法。创建具有公共字符串列表属性的视图模型。如果必须在运行时动态添加名称,请使用 ObservableCollection。现在您在该属性中拥有了列表实例 - 可以在以后随时修改。请参阅编辑后的答案。
  • 这很好用,谢谢。对于我需要的东西来说有点太复杂了,但没关系。我的动态列表是在启动时从系统调用加载的 COMM 端口列表(只读),其余的是 COMM 参数、波特、握手等的静态列表,现在在各种控件中用 XML 硬编码。
  • 不客气。请参阅编辑后的答案以了解另一种更简单的方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-03
  • 2013-06-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多