c# - 在运行时通过 foreach 命令向网格添加多个控件 (C# WPF)

标签 c# wpf foreach grid

亲爱的开发者,美好的一天。我叫丹尼。

这是我在 Stackoverflow 上发表的第一篇文章...因为我对 .NET Framework 还很陌生。 我确实通过几个论坛进行了非常彻底的搜索,但显然是用 Nose 在看。

我的问题是: 我正在编写一段脚本,用于读取一个目录中存在多少个 .txt 文件。

然后它通过“foreach”命令创建与 .txt 一样数量的 GroupBox(每个都有一个网格)。 在同一个 foreach 命令中,我使用:Grid.Children.Add(control)。 每次 foreach 迭代都有 2 个控件要添加到生成的网格中。

问题: 它不会那样做……有点。 它只进行 2 次迭代,无论有多少 .txt。 在输出对话框中它说:A first chance exception of type 'System.ArgumentException' occurred in PresentationCore.dll

如果我的解释没有我想的那么清楚,下面是我的脚本,感谢阅读:

using System;
using System.IO;
using System.Linq;
using System.Windows;
using System.Threading;
using System.Windows.Media;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Threading;
using System.Windows.Controls.Primitives;

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


    // Create Objects
    public TextBox tBox = new TextBox();
    public Label fLabel = new Label();
    public GroupBox group = new GroupBox();
    public Grid groupGrid = new Grid();


    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        // Global Variables
        int Increment = 1;
        string Dir = "DIRECTORY_HERE";
        int leftMargin = 10;
        int betweenMargin = 10;

        // GroupBox Initials
        group.Height = this.Height - 125;
        group.Margin = new Thickness(0, 75, 0, 0);
        group.HorizontalAlignment = HorizontalAlignment.Left;
        group.VerticalAlignment = VerticalAlignment.Top;

        // Grid Initials
        groupGrid.Width = leftMargin;
        groupGrid.Height = group.Height;
        groupGrid.Margin = new Thickness(0, 0, 0, 0);
        groupGrid.HorizontalAlignment = HorizontalAlignment.Left;
        groupGrid.VerticalAlignment = VerticalAlignment.Top;

        // Label Initials
        fLabel.Width = 260;
        fLabel.Height = 28;
        fLabel.HorizontalAlignment = HorizontalAlignment.Left;
        fLabel.VerticalAlignment = VerticalAlignment.Top;
        fLabel.Margin = new Thickness(0, 0, 0, 0);

        // TextBox Intitials
        tBox.Width = 100;
        tBox.Height = groupGrid.Height;
        tBox.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden;
        tBox.HorizontalAlignment = HorizontalAlignment.Left;
        tBox.VerticalAlignment = VerticalAlignment.Top;
        tBox.Foreground = new SolidColorBrush(Colors.DarkCyan);
        tBox.Background = new SolidColorBrush(Colors.Red);
        tBox.TextWrapping = TextWrapping.NoWrap;



        // Get paths of all files within given directory.
        string[] Notes = Directory.GetFiles(Dir, "*.*", SearchOption.TopDirectoryOnly).ToArray();

        foreach (string Note in Notes)
        {
            Console.WriteLine("NOTE: " + Note);

            // Text settings
            FileInfo fi = new FileInfo(Note);
            TextReader tr = new StreamReader(fi.ToString());
            tBox.Name = "tBox" + Increment.ToString();
            tBox.Text = tr.ReadToEnd();
            tBox.Margin = new Thickness(betweenMargin, 0, 0, 0);

            // FileLabel settings
            fLabel.Name = "fLabel" + Increment.ToString();

            // GroupGrid settings
            groupGrid.Name = "groupGrid" + Increment.ToString();
            groupGrid.Children.Add(tBox);               //error
            groupGrid.Children.Add(fLabel);             //error

            // group settings
            group.Width = leftMargin + (tBox.Width * Increment) + (betweenMargin * Increment);
            group.Content = groupGrid;

            // Increment
            Increment++;
        }


        NotesDoc.Children.Add(group);
    }

最佳答案

您正在尝试像编写 Windows 窗体应用程序一样编写 WPF 应用程序。相信我,如果您真正学习了一些 WPF 开发模式(例如 MVVM),您可以更轻松地实现您的目标。

例如,这可以使用 ObservableCollection 轻松完成持有所有(不知道你是怎么做到的)FileInfo实例(每个文件一个)。

然后你bind一个ItemsControlItemsSource此集合的属性。现在,只要您向 ObservableCollection 添加一个新的 FileInfo 实例,ItemsControl 就会添加一个新行并将该行绑定(bind)到该实例。

当然,每一行的默认模板只是在每个实例上调用 .ToString(),所以你会得到一堆包含 "System.IO.FileInfo"的行。要显示有关每个 FileInfo 的信息,您可以添加 DataTemplateItemsSource.ItemTemplate并添加绑定(bind)到 FileInfo 的公共(public)属性的控件。

在开发 WPF 应用程序时,了解其中一些基本模式非常重要。尝试以您尝试的方式从代码隐藏与 UI 交互是非常困难的。 WPF 应用程序的许多 UI 模式都针对在 XAML 中的使用进行了优化;尝试从代码隐藏中与这些(例如附加属性)交互可能会非常困惑和违反直觉。


这是我更新的一个简单的 MVVM 示例。要使用它,请创建一个名为 SimpleMVVM 的新解决方案(3.5 或更高版本)。在项目的根目录中创建一个名为 ViewModel 的新类,并将以下代码添加到文件中:

/// <summary>
/// A simple ViewModel to demonstrate MVVM
/// </summary>
public sealed class ViewModel : DependencyObject, IDataErrorInfo
{
    #region Properties
    #region DirectoryName
    /// <summary>
    /// The <see cref="DependencyProperty"/> for <see cref="DirectoryName"/>.
    /// </summary>
    public static readonly DependencyProperty DirectoryNameProperty =
        DependencyProperty.Register(
            DirectoryNameName,
            typeof(string),
            typeof(ViewModel),
            new UIPropertyMetadata(null));

    /// <summary>
    /// The name of the <see cref="DirectoryName"/> <see cref="DependencyProperty"/>.
    /// </summary>
    public const string DirectoryNameName = "DirectoryName";

    /// <summary>
    /// 
    /// </summary>
    public object DirectoryName
    {
        get { return (object)GetValue(DirectoryNameProperty); }
        set { SetValue(DirectoryNameProperty, value); }
    }
    #endregion

    #region SelectedFile
    /// <summary>
    /// The <see cref="DependencyProperty"/> for <see cref="SelectedFile"/>.
    /// </summary>
    public static readonly DependencyProperty SelectedFileProperty =
        DependencyProperty.Register(
            SelectedFileName,
            typeof(FileInfo),
            typeof(ViewModel),
            new UIPropertyMetadata(null));

    /// <summary>
    /// The name of the <see cref="SelectedFile"/> <see cref="DependencyProperty"/>.
    /// </summary>
    public const string SelectedFileName = "SelectedFile";

    /// <summary>
    /// 
    /// </summary>
    public FileInfo SelectedFile
    {
        get { return (FileInfo)GetValue(SelectedFileProperty); }
        set { SetValue(SelectedFileProperty, value); }
    }
    #endregion


    /// <summary>
    /// The files found under <see cref="DirectoryName"/>.
    /// </summary>
    public ObservableCollection<FileInfo> Files { get; private set; }

    /// <summary>
    /// Holds the last filename error for IDataErrorInfo
    /// </summary>
    private string _lastDirectoryNameError = null;
    #endregion

    #region ctor
    /// <summary>
    /// Initializes a new instance of the <see cref="ViewModel"/> class.
    /// </summary>
    public ViewModel()
    {
        Files = new ObservableCollection<FileInfo>();
    }
    #endregion

    #region methods
    /// <summary>
    /// Invoked whenever the effective value of any dependency property on this <see cref="T:System.Windows.DependencyObject"/> has been updated. The specific dependency property that changed is reported in the event data.
    /// </summary>
    /// <param name="e">Event data that will contain the dependency property identifier of interest, the property metadata for the type, and old and new values.</param>
    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);
        if (e.Property == DirectoryNameProperty)
            UpdateFiles(e.OldValue as string, e.NewValue as string);
    }

    /// <summary>
    /// Updates <see cref="Files"/> when <see cref="DirectoryName"/> changes.
    /// </summary>
    /// <param name="oldDirectoryName">The old value of <see cref="DirectoryName"/></param>
    /// <param name="newDirectoryName">The new value of <see cref="DirectoryName"/></param>
    private void UpdateFiles(string oldDirectoryName, string newDirectoryName)
    {
        if (string.IsNullOrWhiteSpace(newDirectoryName))
        {
            Files.Clear();
            return;
        }
        if (!string.IsNullOrEmpty(oldDirectoryName) &&
            oldDirectoryName.Equals(newDirectoryName, StringComparison.OrdinalIgnoreCase))
            return;

        try
        {
            var di = new DirectoryInfo(Directory.Exists(newDirectoryName) ? newDirectoryName : Path.GetDirectoryName(newDirectoryName));
            // dirty hack
            if (di.ToString().Equals(".", StringComparison.OrdinalIgnoreCase))
            {
                _lastDirectoryNameError = "Not a valid directory name.";
                return;
            }
            Files.Clear();
            foreach (var file in di.GetFiles())
                Files.Add(file);
            _lastDirectoryNameError = null;
        }
        catch (Exception ioe)
        {
            _lastDirectoryNameError = ioe.Message;
        }
    }
    #endregion

    #region IDataErrorInfo
    /// <summary>
    /// Gets an error message indicating what is wrong with this object.
    /// </summary>
    /// <returns>An error message indicating what is wrong with this object. The default is an empty string ("").</returns>
    string IDataErrorInfo.Error
    {
        get
        {
            return _lastDirectoryNameError;
        }
    }

    /// <summary>
    /// Gets the error message for the property with the given name.
    /// </summary>
    /// <returns>The error message for the property. The default is an empty string ("").</returns>
    string IDataErrorInfo.this[string columnName]
    {
        get
        {
            if (columnName.Equals(DirectoryNameName, StringComparison.OrdinalIgnoreCase))
                return _lastDirectoryNameError;
            return null;
        }
    }
    #endregion
}

接下来,打开 MainWindow.xaml 并将 xaml 替换为以下内容:

<Window
    x:Class="SimpleMVVM.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:t="clr-namespace:SimpleMVVM"
    Title="MainWindow"
    Height="350"
    Width="525">
    <Window.DataContext>
        <t:ViewModel />
    </Window.DataContext>
    <Window.Resources>
        <Style
            x:Key="alternatingListViewItemStyle"
            TargetType="{x:Type ListViewItem}">
            <Style.Triggers>
                <Trigger
                    Property="ItemsControl.AlternationIndex"
                    Value="1">
                    <Setter
                        Property="Background"
                        Value="LightGray"></Setter>
                </Trigger>
                <Trigger
                    Property="ItemsControl.AlternationIndex"
                    Value="2">
                    <Setter
                        Property="Background"
                        Value="White"></Setter>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition
                Height="auto" />
            <RowDefinition />
            <RowDefinition
                Height="auto" />
        </Grid.RowDefinitions>
        <TextBox
            Margin="4"
            Text="{Binding DirectoryName, ValidatesOnDataErrors=True, ValidatesOnExceptions=True,UpdateSourceTrigger=PropertyChanged}">
            <TextBox.Style>
                <Style
                    TargetType="TextBox">
                    <Setter
                        Property="ToolTip"
                        Value="Please enter a directory name" />
                    <Style.Triggers>
                        <Trigger
                            Property="Validation.HasError"
                            Value="true">
                            <Setter
                                Property="ToolTip"
                                Value="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={x:Static RelativeSource.Self}}" />
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </TextBox.Style>
        </TextBox>
        <ListView
            Margin="4"
            Grid.Row="1"
            AlternationCount="2"
            ItemsSource="{Binding Files}"
            ItemContainerStyle="{StaticResource alternatingListViewItemStyle}"
            SelectedItem="{Binding SelectedFile}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition />
                            <RowDefinition />
                            <RowDefinition />
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition
                                Width="100" />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>
                        <Label
                            Grid.Row="0">Name</Label>
                        <Label
                            Grid.Row="1">Size</Label>
                        <Label
                            Grid.Row="2">Read Only</Label>
                        <Label
                            Grid.Row="3">Type</Label>
                        <TextBlock
                            Grid.Row="0"
                            Grid.Column="1"
                            Text="{Binding Name}" />
                        <TextBlock
                            Grid.Row="1"
                            Grid.Column="1"
                            Text="{Binding Length}" />
                        <CheckBox
                            Grid.Row="2"
                            Grid.Column="1"
                            IsChecked="{Binding IsReadOnly}"
                            IsEnabled="False" />
                        <TextBlock
                            Grid.Row="3"
                            Grid.Column="1"
                            Text="{Binding Extension}" />
                    </Grid>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <StatusBar
            Grid.Row="2">
            <StatusBarItem
                Content="{Binding SelectedFile.FullName, StringFormat='Selected: {0}', FallbackValue='Please enter a directory and select a file.'}" />
        </StatusBar>
    </Grid>
</Window>

(请大家原谅代码转储!)编译、修复错误并运行它。

关于c# - 在运行时通过 foreach 命令向网格添加多个控件 (C# WPF),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5476506/

相关文章:

c# - .NET Dictionary 实现如何处理可变对象

c# - Angular 6 向 .NET Core API 发出请求

c# - 为什么我在 C# 编译器中收到此错误 "Unassigned Local Variable"

c# - 为什么 .Net 没有 Thread.Start() 的通用版本?

c# - 如何避免多个 View 从同一个 View 模型接收数据

wpf - 如何使用MVVM创建通知控件?

c++使用foreach使数组为空

javascript - 检查 Angular 响应式(Reactive)表单中的 FormGroups 字段中是否存在重复项

c# - WPF Caliburn Micro Message.Attach 未通过 HierarchicalDataTemplate 冒泡

foreach - 首选 foreach 索引类型