简体   繁体   中英

How can I fill empty space with rectangles in WPF?

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.

在此处输入图片说明

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

在此处输入图片说明

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:

在此处输入图片说明

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);
            }
        }
    }
}

Instead of trying to figure out where your empty spaces are just assume everywhere inside of your custruction square is empty space and set the Background of the Parant container to white. assuming you have a parent containing all your blocks..if not you should consider it..

In wpf you can aslo combine gemoetrys check out this article it may help a lot: http://www.blackwasp.co.uk/WPFCombinedGeometry.aspx

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM