首页 > c# > 如何在WPF中用矩形填充空白区域?

如何在WPF中用矩形填充空白区域? (How can I fill empty space with rectangles in WPF?)

2019-03-04 c#wpfxaml2d

问题

我正在构建一个程序来查看和编辑3D程序中的橱柜/家具。

一个功能要求是制作团队能够以2D方式查看机柜的表面,并且列出了开口的大小以便于查看。我正在弄清楚我的开口大小并添加一个这样大小的矩形。目标是在显示开口大小的白色矩形中添加文本,以便他们可以构建适合其内部的项目。

我能够在抽屉柜上看到开口,见下文。

在此输入图像描述

对于像下面那样复杂的那些对我来说有点困难。

在此输入图像描述

以下是棕色部分的属性:

public double X {get; 组; }

公共双Y {得; 组; }

public double Width {get; 组; }

公共双高度{得; 组; }

我的XAML很简单,只需将所有矩形添加到画布并使用X,Y定位。

我的代码有点乱,但它在下面。

//Filter list of parts to get the frame parts
int Counter = 0;
var frameParts = getFrameParts.Where(p => p.CatalogPartID == 1015 || p.CatalogPartID == 1016 || p.CatalogPartID == 3025).OrderBy(p => p.CatalogPartID).OrderBy(p => p.Y).ToList();
MoFacePart previousFrameRail = new MoFacePart();
MoFacePart previousMidFrameStile = new MoFacePart();

foreach (var part in frameParts)
{
        var totalParts = getFrameParts.Where(p => p.CatalogPartID == 1016).ToList().Count();
        // Adding Horizontal Spaces
        if (part.CatalogPartID == 1016)
        {
            var newOpening = new MoFacePart { Width = part.Width, Height = (previousFrameRail.Y - previousFrameRail.Height) - (130-(part.Y + part.Height)), X = ((80 - (double)SelectedViewerProduct.Width) / 2) + part.X, Y = (previousFrameRail.Y - previousFrameRail.Height), Fill = new SolidColorBrush(System.Windows.Media.Color.FromRgb(255, 255, 255)) };
            if (Counter > 0 && Counter < (totalParts))
            {
                FaceParts.Add(newOpening);
            }
            Counter++;
        }

        var newPart = new MoFacePart { Width = part.Width, Height = part.Height, X = ((80 - (double)SelectedViewerProduct.Width) / 2) + part.X, Y = 130 - part.Y, Fill = new SolidColorBrush(System.Windows.Media.Color.FromRgb(210, 180, 140)) };
        FaceParts.Add(newPart);
        if (part.CatalogPartID == 1016)
        {
            previousFrameRail = newPart;
        }

}

鉴于所有这一切,有没有更好的方法来找出所有空的空间?

我为作为图像插入的代码道歉。我无法正确格式化以允许我发布。

我有信心我可以找到一个解决方案正在做我正在做的事情,但我觉得有更好的方法,我错过了它。

谢谢!

解决方法

这看起来很有趣,所以这里是一个答案。我只是在画布上放置矩形作为我的源数据。有关详细信息,请参阅代码中的注释。它可能需要一些调整,我只用你在XAML中看到的矩形测试它。

截图:

在此输入图像描述

XAML

<Window x:Class="StackOverflow54985848.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"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Canvas x:Name="canvas" Margin="10">
        <Canvas.Resources>
            <Style TargetType="Rectangle">
                <Setter Property="Fill" Value="Tan" />
                <Setter Property="StrokeThickness" Value="1" />
                <Setter Property="Stroke" Value="Black" />
            </Style>
        </Canvas.Resources>
        <!-- Outside frame -->
        <Rectangle Canvas.Left="0" Canvas.Top="0" Width="10" Height="300" />
        <Rectangle Canvas.Left="300" Canvas.Top="0" Width="10" Height="300" />
        <Rectangle Canvas.Left="10" Canvas.Top="0" Width="290" Height="10" />
        <Rectangle Canvas.Left="10" Canvas.Top="290" Width="290" Height="10" />

        <!-- Insides -->
        <Rectangle Canvas.Left="10" Canvas.Top="75" Width="290" Height="10" />
        <Rectangle Canvas.Left="100" Canvas.Top="85" Width="10" Height="205" />
        <Rectangle Canvas.Left="10" Canvas.Top="175" Width="90" Height="10" />
    </Canvas>
</Window>

码:

using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

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

            // Get the rectangles from the canvas
            var rects = canvas.Children
                .Cast<Rectangle>()
                .Select(r => new Rect(
                    (double)r.GetValue(Canvas.LeftProperty),
                    (double)r.GetValue(Canvas.TopProperty),
                    r.Width, r.Height))
                    .ToArray();

            // Determine the bounds of the rects
            var minX = rects.Min(r => r.Left);
            var maxX = rects.Max(r => r.Right);
            var minY = rects.Min(r => r.Top);
            var maxY = rects.Max(r => r.Bottom);
            var bounds = new Rect(minX, minY, maxX - minX, maxY - minY);

            // openSpace initially is the entire area
            List<Rect> openSpace = new List<Rect>() { bounds };

            // Remove r from all rects in openSpace
            foreach (var r in rects)
            {
                List<Rect> openSpaceToRemove = new List<Rect>();
                List<Rect> openSpaceToAdd = new List<Rect>();

                foreach (var os in openSpace)
                {
                    if (!os.IntersectsWith(r))
                        continue;
                    var r2 = os;
                    r2.Intersect(r); // result stored in r2, it is the area that isn't open anymore

                    // We will be removing os since it intersects
                    openSpaceToRemove.Add(os);

                    // Remove r2 from os
                    //
                    // Probably a better way to do this...
                    // We have the area that ISNT open (r2) but we want the area that IS open still
                    // Create 4 rects that cover the area OUTSIDE of r2 (to the left, right, above, below)
                    // The intersection of those rects and os is our still open space (subset of os)

                    // Create a rect that is everything to the left of r2 and intersect it with os
                    var rr = new Rect(bounds.Left, bounds.Top, r2.Left, bounds.Height);
                    rr.Intersect(os); // intersection is stored in rr
                    if (rr.Width > 0 && rr.Height > 0)
                        openSpaceToAdd.Add(rr);

                    // Repeat with everything to the right
                    rr = new Rect(r2.Right, bounds.Top, bounds.Right - r2.Right, bounds.Height);
                    rr.Intersect(os); // intersection is stored in rr
                    if (rr.Width > 0 && rr.Height > 0)
                        openSpaceToAdd.Add(rr);

                    // Repeat with everything above the top
                    rr = new Rect(bounds.Left, r2.Top - bounds.Height, bounds.Width, bounds.Height);
                    rr.Intersect(os); // intersection is stored in rr
                    if (rr.Width > 0 && rr.Height > 0)
                        openSpaceToAdd.Add(rr);

                    // Repeat with everything below the bottom
                    rr = new Rect(bounds.Left, r2.Bottom, bounds.Width, bounds.Height);
                    rr.Intersect(os); // intersection is stored in rr
                    if (rr.Width > 0 && rr.Height > 0)
                        openSpaceToAdd.Add(rr);
                }

                // Remove rects we don't want
                foreach (var os in openSpaceToRemove)
                    openSpace.Remove(os);
                // Add rects we do want
                openSpace.AddRange(openSpaceToAdd);
            }

            // Merge openSpace entries
            for (int i = 0; i < openSpace.Count; i++)
            {
                // Get an openSpace rect
                var r = openSpace[i];
                // Loop through the rects that come after it
                for (int j = i + 1; j < openSpace.Count; j++)
                {
                    // Get the next rect
                    var c = openSpace[j];
                    // If c or r contains each other then expand r to contain both and remove c
                    if (r.Contains(c) || c.Contains(r))
                    {
                        r.Union(c);
                        openSpace[i] = r;
                        openSpace.RemoveAt(j);
                        // start over since r changed and we removed openSpace at index j
                        // set j = i so when the loop counter increments, j will equal i + 1
                        j = i;
                    }
                }
            }
            // Remove duplicates?
            openSpace = openSpace.Distinct().ToList();

            // Now that our openspace has been determined, add it to the canvas
            foreach (var r in openSpace)
            {
                var rr = new Rectangle()
                {
                    Width = r.Width,
                    Height = r.Height,
                    Fill = Brushes.Beige,
                    Stroke = Brushes.Red,
                    StrokeThickness = 1.0
                };
                rr.SetValue(Canvas.LeftProperty, r.Left);
                rr.SetValue(Canvas.TopProperty, r.Top);
                canvas.Children.Add(rr);

                // Grid to hold the textblock (more control over width/height)
                var grid = new Grid()
                {
                    Width = r.Width,
                    Height = r.Height,
                };
                grid.SetValue(Canvas.LeftProperty, r.Left);
                grid.SetValue(Canvas.TopProperty, r.Top);
                TextBlock tb = new TextBlock()
                {
                    Text = $"Width: {rr.Width} Height: {rr.Height}",
                    Foreground = Brushes.Red,
                    VerticalAlignment = VerticalAlignment.Center,
                    HorizontalAlignment = HorizontalAlignment.Center,
                    TextWrapping = TextWrapping.Wrap
                };
                grid.Children.Add(tb);
                canvas.Children.Add(grid);
            }
        }
    }
}

问题

I'm building a program to view and edit Cabinets/furniture in a 3D program.

One feature request was for the production team to be able to view the face of the cabinet in 2D and have the size of the openings listed for easy viewing. I'm figuring out the size of my openings and adding a rectangle of that size. The goal is to add text to the white rectangle displaying the size of the opening so they can build items to fit inside of it.

I was able to get the openings on a drawer cabinet, see below.

enter image description here

For the more complicated ones like the one below it is a bit more difficult for me.

enter image description here

Here are the properties that are on the brown parts:

public double X { get; set; }

public double Y { get; set; }

public double Width { get; set; }

public double Height { get; set; }

My XAML is simple, just adding all the rectangles to a canvas and positioning with X,Y.

My code is a bit more of a mess but it is below.

//Filter list of parts to get the frame parts
int Counter = 0;
var frameParts = getFrameParts.Where(p => p.CatalogPartID == 1015 || p.CatalogPartID == 1016 || p.CatalogPartID == 3025).OrderBy(p => p.CatalogPartID).OrderBy(p => p.Y).ToList();
MoFacePart previousFrameRail = new MoFacePart();
MoFacePart previousMidFrameStile = new MoFacePart();

foreach (var part in frameParts)
{
        var totalParts = getFrameParts.Where(p => p.CatalogPartID == 1016).ToList().Count();
        // Adding Horizontal Spaces
        if (part.CatalogPartID == 1016)
        {
            var newOpening = new MoFacePart { Width = part.Width, Height = (previousFrameRail.Y - previousFrameRail.Height) - (130-(part.Y + part.Height)), X = ((80 - (double)SelectedViewerProduct.Width) / 2) + part.X, Y = (previousFrameRail.Y - previousFrameRail.Height), Fill = new SolidColorBrush(System.Windows.Media.Color.FromRgb(255, 255, 255)) };
            if (Counter > 0 && Counter < (totalParts))
            {
                FaceParts.Add(newOpening);
            }
            Counter++;
        }

        var newPart = new MoFacePart { Width = part.Width, Height = part.Height, X = ((80 - (double)SelectedViewerProduct.Width) / 2) + part.X, Y = 130 - part.Y, Fill = new SolidColorBrush(System.Windows.Media.Color.FromRgb(210, 180, 140)) };
        FaceParts.Add(newPart);
        if (part.CatalogPartID == 1016)
        {
            previousFrameRail = newPart;
        }

}

Given all of this, is there a better way to figure out all the empty spaces?

I apologize for the code being inserted as an image. I could not get it to properly format to allow me to post.

I'm confident I can find a solution doing what i'm doing but I feel like there is a better way and i'm missing it.

Thanks!

解决方法

This looked like fun so here is an answer. I just put rectangles on a canvas for my source data. See the comments in the code for details. It may need some tweaking, I only tested it with the rectangles you see in the XAML.

Screenshot:

enter image description here

XAML

<Window x:Class="StackOverflow54985848.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"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Canvas x:Name="canvas" Margin="10">
        <Canvas.Resources>
            <Style TargetType="Rectangle">
                <Setter Property="Fill" Value="Tan" />
                <Setter Property="StrokeThickness" Value="1" />
                <Setter Property="Stroke" Value="Black" />
            </Style>
        </Canvas.Resources>
        <!-- Outside frame -->
        <Rectangle Canvas.Left="0" Canvas.Top="0" Width="10" Height="300" />
        <Rectangle Canvas.Left="300" Canvas.Top="0" Width="10" Height="300" />
        <Rectangle Canvas.Left="10" Canvas.Top="0" Width="290" Height="10" />
        <Rectangle Canvas.Left="10" Canvas.Top="290" Width="290" Height="10" />

        <!-- Insides -->
        <Rectangle Canvas.Left="10" Canvas.Top="75" Width="290" Height="10" />
        <Rectangle Canvas.Left="100" Canvas.Top="85" Width="10" Height="205" />
        <Rectangle Canvas.Left="10" Canvas.Top="175" Width="90" Height="10" />
    </Canvas>
</Window>

Code:

using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;

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

            // Get the rectangles from the canvas
            var rects = canvas.Children
                .Cast<Rectangle>()
                .Select(r => new Rect(
                    (double)r.GetValue(Canvas.LeftProperty),
                    (double)r.GetValue(Canvas.TopProperty),
                    r.Width, r.Height))
                    .ToArray();

            // Determine the bounds of the rects
            var minX = rects.Min(r => r.Left);
            var maxX = rects.Max(r => r.Right);
            var minY = rects.Min(r => r.Top);
            var maxY = rects.Max(r => r.Bottom);
            var bounds = new Rect(minX, minY, maxX - minX, maxY - minY);

            // openSpace initially is the entire area
            List<Rect> openSpace = new List<Rect>() { bounds };

            // Remove r from all rects in openSpace
            foreach (var r in rects)
            {
                List<Rect> openSpaceToRemove = new List<Rect>();
                List<Rect> openSpaceToAdd = new List<Rect>();

                foreach (var os in openSpace)
                {
                    if (!os.IntersectsWith(r))
                        continue;
                    var r2 = os;
                    r2.Intersect(r); // result stored in r2, it is the area that isn't open anymore

                    // We will be removing os since it intersects
                    openSpaceToRemove.Add(os);

                    // Remove r2 from os
                    //
                    // Probably a better way to do this...
                    // We have the area that ISNT open (r2) but we want the area that IS open still
                    // Create 4 rects that cover the area OUTSIDE of r2 (to the left, right, above, below)
                    // The intersection of those rects and os is our still open space (subset of os)

                    // Create a rect that is everything to the left of r2 and intersect it with os
                    var rr = new Rect(bounds.Left, bounds.Top, r2.Left, bounds.Height);
                    rr.Intersect(os); // intersection is stored in rr
                    if (rr.Width > 0 && rr.Height > 0)
                        openSpaceToAdd.Add(rr);

                    // Repeat with everything to the right
                    rr = new Rect(r2.Right, bounds.Top, bounds.Right - r2.Right, bounds.Height);
                    rr.Intersect(os); // intersection is stored in rr
                    if (rr.Width > 0 && rr.Height > 0)
                        openSpaceToAdd.Add(rr);

                    // Repeat with everything above the top
                    rr = new Rect(bounds.Left, r2.Top - bounds.Height, bounds.Width, bounds.Height);
                    rr.Intersect(os); // intersection is stored in rr
                    if (rr.Width > 0 && rr.Height > 0)
                        openSpaceToAdd.Add(rr);

                    // Repeat with everything below the bottom
                    rr = new Rect(bounds.Left, r2.Bottom, bounds.Width, bounds.Height);
                    rr.Intersect(os); // intersection is stored in rr
                    if (rr.Width > 0 && rr.Height > 0)
                        openSpaceToAdd.Add(rr);
                }

                // Remove rects we don't want
                foreach (var os in openSpaceToRemove)
                    openSpace.Remove(os);
                // Add rects we do want
                openSpace.AddRange(openSpaceToAdd);
            }

            // Merge openSpace entries
            for (int i = 0; i < openSpace.Count; i++)
            {
                // Get an openSpace rect
                var r = openSpace[i];
                // Loop through the rects that come after it
                for (int j = i + 1; j < openSpace.Count; j++)
                {
                    // Get the next rect
                    var c = openSpace[j];
                    // If c or r contains each other then expand r to contain both and remove c
                    if (r.Contains(c) || c.Contains(r))
                    {
                        r.Union(c);
                        openSpace[i] = r;
                        openSpace.RemoveAt(j);
                        // start over since r changed and we removed openSpace at index j
                        // set j = i so when the loop counter increments, j will equal i + 1
                        j = i;
                    }
                }
            }
            // Remove duplicates?
            openSpace = openSpace.Distinct().ToList();

            // Now that our openspace has been determined, add it to the canvas
            foreach (var r in openSpace)
            {
                var rr = new Rectangle()
                {
                    Width = r.Width,
                    Height = r.Height,
                    Fill = Brushes.Beige,
                    Stroke = Brushes.Red,
                    StrokeThickness = 1.0
                };
                rr.SetValue(Canvas.LeftProperty, r.Left);
                rr.SetValue(Canvas.TopProperty, r.Top);
                canvas.Children.Add(rr);

                // Grid to hold the textblock (more control over width/height)
                var grid = new Grid()
                {
                    Width = r.Width,
                    Height = r.Height,
                };
                grid.SetValue(Canvas.LeftProperty, r.Left);
                grid.SetValue(Canvas.TopProperty, r.Top);
                TextBlock tb = new TextBlock()
                {
                    Text = $"Width: {rr.Width} Height: {rr.Height}",
                    Foreground = Brushes.Red,
                    VerticalAlignment = VerticalAlignment.Center,
                    HorizontalAlignment = HorizontalAlignment.Center,
                    TextWrapping = TextWrapping.Wrap
                };
                grid.Children.Add(tb);
                canvas.Children.Add(grid);
            }
        }
    }
}
相似信息