【问题标题】:How to make a grid of similar looking objects?如何制作相似外观对象的网格?
【发布时间】:2019-11-08 12:13:32
【问题描述】:

我即将编写一些桌面应用程序来模拟某些东西,我认为这是尝试一些新技术的好机会。由于应用程序适用于 Windows,并且我在某处看到了 Visual Studio 社区版,因此我决定尝试 WPF。

所以事情就是这样。基本视图应该看起来像一个简单的网格,其中每个矩形都是一个实际的文本框。我可以点击并在每一个中写一些文字。

这还不算太糟糕。起初我在玩 Grid 和 ColumnDefinitions 和 RowDefinitions,这对硬编码的很有用。之后,我尝试了 ItemsControl 和 ItemTemplate,就这样。

但现在剧情出现了转折。我希望能够编辑每一个文本框。编辑我的意思是把它分成几个更小的文本框。因此,如果我将第 2 个分成 2 块,将第 3 个分成 3 块,它应该看起来像:

我不知道如何解决这个问题。由于它与其他的不同,我认为我不能再将 ItemsControl 与模板一起使用(或者我可以吗?)。我对 WPF 很陌生,所以也许有一些我还没有看到的明显的东西。因此,如果有人非常了解 WPF 并可以为我指明正确的方向,或者至少告诉我“你在做什么?WPF 不适合那种应用程序,请改用 XXX”。

【问题讨论】:

  • 嗯...我可能看到一个 UniformGrid 有 4 列和 8 个 EditSplitterControl 类型的子项(您创建),它是您的 TextBoxes 的 StackPanel(您将放入 UserControl 以提供一些命令像拆分/关闭),它将添加/减去用于填充 StackPanel (ItemsControl) 的 Items ObservableCollection。在模拟这个之前,我必须多做一点,但也许这就是你正在寻找的挑战并且不想要示例代码。可能还有几种/十几种其他方法可以做到这一点。
  • 谢谢大家。我发现这两个答案都非常有用。太糟糕了,我不能接受这两个答案。感谢您的帮助。

标签: c# wpf wpf-controls


【解决方案1】:

好问题,当然有很多方法可以根据您的应用将执行的操作以及如何将每个控件的数据连接到您的应用/模型/其他内容来布局控件。

此答案侧重于布局,同时占用所有可用空间并允许复制每个容器中的内容。内容流畅地流动,直到窗口变得太小、太窄等等,这就是您需要决定您的应用程序将允许用户做什么的地方。在此代码达到生产质量之前还有很多工作要做,但它是熟悉 WPF 平台的一些 WPF 基础知识的一个很好的示例。

我为测试添加了一些边框、边距和背景颜色,这样您就可以看到哪个容器占用了多少空间。适合测试;在最终版本中可能会删除或更改为透明。

主窗口

XAML

<Grid x:Name="LayoutRoot">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <StackPanel>
        <TextBlock Text="WPF" FontSize="36" Margin="20" Foreground="Orange" HorizontalAlignment="Center"/>
    </StackPanel>
    <Grid Grid.Row="1" Margin="5">
        <Border Background="LightGray" BorderBrush="Red" BorderThickness="1">
            <UniformGrid Columns="4" Name="MainPanel"/>
        </Border>
    </Grid>
</Grid>

代码

public partial class MainWindow : Window
{
    private static int _nextId = 0;
    public static int NextId
    {
        get { return _nextId++; }
    }

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
        Loaded += MainWindow_Loaded;
    }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        // add non-multiple of 8 to see how layout works
        for (var i=0; i<7; i++)
        {
            MainPanel.Children.Add(new EditPanelControl());
        }
    }
}

EditPanelControl(用户控件)

XAML

<Grid Margin="5">
    <Border Background="LightYellow" BorderBrush="Green" BorderThickness="1">
        <!-- Make this a viewbox if you want to show all items but have them shrink -->
        <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
            <StackPanel Name="MainPanel" VerticalAlignment="Center"/>
        </ScrollViewer>
    </Border>
</Grid>

代码

public partial class EditPanelControl : UserControl
{
    public EditPanelControl()
    {
        InitializeComponent();
        DataContext = this;
        Loaded += EditPanelControl_Loaded;
    }

    private void EditPanelControl_Loaded(object sender, RoutedEventArgs e)
    {
        AddSuperTextControl();
    }

    private void AddSuperTextControl()
    {
        var stc = new SuperTextControl();
        stc.SplitEvent += Stc_SplitEvent;
        stc.DeleteEvent += Stc_DeleteEvent;
        stc.SuperTextBox.Text = MainWindow.NextId.ToString();
        MainPanel.Children.Add(stc);
    }

    private void Stc_DeleteEvent(object sender, EventArgs e)
    {
        // todo: don't allow delete if only 1 child
        var stc = (SuperTextControl)sender;
        MainPanel.Children.Remove(stc);
    }

    private void Stc_SplitEvent(object sender, EventArgs e)
    {
        var stc = (SuperTextControl)sender; // fyi
        AddSuperTextControl();
    }
}

SuperTextControl(用户控件)

XAML

<Grid Margin="5">
    <Border Background="Wheat" BorderBrush="Blue" BorderThickness="1">
        <StackPanel VerticalAlignment="Center" HorizontalAlignment="Stretch">
            <TextBox Name="SuperTextBox" Margin="5"/>
            <DockPanel LastChildFill="False" Margin="0,0,0,5">
                <Button Content="Split" Click="SplitHandler" Margin="5,0" DockPanel.Dock="Left"/>
                <Button Content="Delete" Click="DeleteHandler" Margin="5,0" DockPanel.Dock="Right"/>
            </DockPanel>
        </StackPanel>
    </Border>
</Grid>

代码

public partial class SuperTextControl : UserControl
{
    public event EventHandler SplitEvent;
    public event EventHandler DeleteEvent;

    public SuperTextControl()
    {
        InitializeComponent();
    }

    private void SplitHandler(object sender, RoutedEventArgs e)
    {
        var button = (Button)sender; // fyi

        if (SplitEvent != null)
        {
            SplitEvent(this, new EventArgs());
        }
    }

    private void DeleteHandler(object sender, RoutedEventArgs e)
    {
        var button = (Button)sender; // fyi

        if (DeleteEvent != null)
        {
            DeleteEvent(this, new EventArgs());
        }
    }
}

【讨论】:

  • 嗨,Kory,感谢您的时间和回答。其实远远超出预期。我希望有一些我应该查找的提示或组件名称,但我为我的实验奠定了坚实的基础。很好的概念证明和调试内容(边框、背景颜色等)非常简洁。再次感谢,非常感谢您的帮助。
【解决方案2】:

您可以使用ÌtemTemplateSelector 根据变量分配不同的模板。

在您后面的代码中,我们有一个简单的Rows 属性。这将用于显示 Grid 中显示了多少行。

MainWindow.cs

public partial class MainWindow : Window
{
    public int[] Rows{ get; set; }
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
        this.Rows= new int[] { 1, 2, 1, 3 };
    }
}

根据Rows选择正确的DataTemplate的ItemTemplateSelector.cs

public class RowTemplateSelecter: DataTemplateSelector
{        
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        FrameworkElement element = container as FrameworkElement;
        int rows = 1;
        int.TryParse(item.ToString(), out rows);
        switch (rows)
        {
            case 1:
                return element.FindResource("OneRow") as DataTemplate;
            case 2:
                return element.FindResource("TwoRows") as DataTemplate;
            case 3:
                return element.FindResource("ThreeRows") as DataTemplate;
            default:
                return element.FindResource("OneRow") as DataTemplate;

        }          
    }
}

最后是 MainWindow.xaml,我们在其中添加了 3 个模板和 ItemTemplateSelector

<Window.Resources>
    <DataTemplate x:Key="OneRow">
        <Grid Background="Red" Width="100" Height="100">

        </Grid>
    </DataTemplate>
    <DataTemplate x:Key="TwoRows" >
        <Grid Width="100" Height="100">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Rectangle Fill="Blue"/>
            <Rectangle Fill="Green" Grid.Row="1"/>
        </Grid>
    </DataTemplate>
    <DataTemplate x:Key="ThreeRows">
        <Grid Width="100" Height="100">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Rectangle Fill="Yellow"/>
            <Rectangle Fill="Orange" Grid.Row="1"/>
            <Rectangle Fill="Black" Grid.Row="2"/>
        </Grid>
    </DataTemplate>
    <local:RowTemplateSelecter x:Key="RowSelector"/>
</Window.Resources>
<Grid>
    <ItemsControl x:Name="rectangles"
                  ItemsSource="{Binding Rows}"
                  ItemTemplateSelector="{StaticResource RowSelector}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</Grid>

希望对你有帮助

【讨论】:

  • 感谢 SWilko 的回答。这是一个很好的例子,它完全符合我的要求。我唯一担心的是,如果我想引入更多“模板”(如垂直分割、不同比率等),我的 MainWindow.xaml 会变得非常大。可能有一种方法可以以某种方式将其移出,但这可能是其他时间的问题。抱歉,我会接受另一个(因为它更灵活),但再次感谢,非常感谢您的回答。
猜你喜欢
  • 2019-03-20
  • 1970-01-01
  • 2012-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多