Reputation: 3
I'm working on a Blazor project where I need to place objects (like bread, watermelon) on a background image. The problem is that when I move the objects, they appear underneath the image rather than on top of it where I want them. Here's an overview of my implementation and the problem:
I have a Blazor component where users can drag and drop objects onto a background image. However, the objects are not placed directly on the image but seem to appear underneath it when moved. I want the objects to be positioned exactly on the image, not beneath it.
Here is the relevant part of my code:
@page "/planDisplay"
@using siteplan_Projekt.Data
@inject FileService fileService
@inject IJSRuntime JsRuntime
@rendermode InteractiveServer
<PageTitle>Plan-Display</PageTitle>
@if (errors.Count > 0)
{
<ul class="text-danger">
@foreach (var error in errors)
{
<li>@error</li>
}
</ul>
}
<!-- siteplan-Projekt/Components/Pages/SitePlan/PlanDisplay.razor -->
<MudDropContainer T="DropItem" Items="_items" ApplyDropClassesOnDragStarted="_applyDropClassesOnDragStarted" ItemsSelector="@((item, dropzone) => item.Place == dropzone)" ItemDropped="ItemUpdated" Class="d-flex flex-column flex-grow-1">
<ChildContent>
<div class="d-flex flex-wrap justify-space-between" @onmousemove="OnMouseMove">
<MudDropZone T="DropItem" Identifier="ObjBox" CanDrop="@(item => false)" Class="rounded-lg border-2 border-solid mud-border-lines-default pa-6 ma-8">
<MudText Typo="Typo.h6" Class="mb-4">Objekt</MudText>
</MudDropZone>
<div class="image-wrapper" style="position: relative; width: 800px; height: 600px;">
<MudDropZone T="DropItem" Identifier="Image" CanDrop="@(item => item.IsPicked == false && item.IsRotten == false)" Class="site-plan-dropzone">
<img src="@imageUpload[0].Url" alt="Site plan" class="site-plan" @onclick="onImageClick" style="width: 100%; height: 100%;"/>
</MudDropZone>
<svg class="grid-overlay" xmlns="http://www.w3.org/2000/svg" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;">
@if (selectedPoints.Count == 1 && tempPoint != null)
{
<line x1="@selectedPoints[0].X" y1="@selectedPoints[0].Y" x2="@tempPoint.Value.X" y2="@tempPoint.Value.Y" stroke="red" stroke-width="3"/>
}
@if (selectedPoints.Count == 2)
{
<line x1="@selectedPoints[0].X" y1="@selectedPoints[0].Y" x2="@selectedPoints[1].X" y2="@selectedPoints[1].Y" stroke="red" stroke-width="3"/>
}
@if (selectedPoints.Count <= 2)
{
@foreach (var point in selectedPoints)
{
<circle cx="@point.X" cy="@point.Y" r="5" fill="blue"></circle>
}
}
</svg>
</div>
</div>
</ChildContent>
<ItemRenderer>
<MudPaper Height="54px" Width="54px" Class="pa-2 icon-over-image" Elevation="0">
<MudIcon Icon="@context.Icon"/>
</MudPaper>
</ItemRenderer>
</MudDropContainer>
@if (selectedPoints.Count == 2)
{
<MudItem xs="12">
<MudTextField @bind-Value="realDistance" Label="Real Distance (meters)"/>
<MudButton OnClick="calculateScale">Calculate Scale</MudButton>
</MudItem>
}
<MudItem xs="12">
<MudText>Scale: @scaleManager.getScale() pixels per meter</MudText>
</MudItem>
<MudToolBar>
<MudSpacer/>
<MudButton OnClick="Reset">Reset</MudButton>
</MudToolBar>
using System.Drawing;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
using MudBlazor;
using siteplan_Projekt.Data;
using siteplan_Projekt.Models;
namespace siteplan_Projekt.Components.Pages.SitePlan;
/// <summary>
/// This class is responsible for displaying the plan.
/// It initializes and manages the display of uploaded images,
/// and handles rendering behavior to ensure proper initialization.
/// </summary>
public partial class PlanDisplay
{
#region Protected
/// <summary>
/// Called when the component is initialized.
/// Populates the `imageUpload` list with files retrieved from the `fileService`.
/// </summary>
protected override void OnInitialized()
{
try
{
imageUpload = fileService.GetUploadedFiles();
var dimensions = fileService.GetImageDimensions(imageUpload[0].Name);
imageWidth = dimensions.width;
imageHeight = dimensions.height;
}
catch (Exception e)
{
Console.WriteLine($"Error: {e.Message}");
}
}
/// <summary>
/// Asynchronously called after the component has rendered.
/// Executes actions that should only occur during the first render.
/// If `firstRender` is true and `isFirstRender` is true,
/// sets `isFirstRender` to `false` to prevent the first render
/// logic from running again.
/// </summary>
/// ///
/// <param name="CamBox">
/// True if this is the first time the component is rendered;
/// false if it has been rendered before.
/// </param>
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && isFirstRender) isFirstRender = false;
}
#endregion
#region Private
private readonly List<PointF> selectedPoints = new();
private readonly Dictionary<string, object> svgAttributes = new();
private readonly List<PointF> objectPoints = new();
/// <summary>
/// A list to store any errors encountered during processing.
/// </summary>
private readonly List<string> errors = new();
private readonly bool _applyDropClassesOnDragStarted = false;
private readonly List<DropItem> _items = new()
{
new DropItem { Icon = Icons.Custom.Uncategorized.ChessKing, IsRotten = false, Place = "ObjBox" },
new DropItem { Icon = Icons.Custom.Uncategorized.Baguette, IsRotten = false, Place = "ObjBox" },
new DropItem { Icon = Icons.Custom.Uncategorized.Sausage, IsRotten = true, Place = "ObjBox" },
new DropItem { Icon = Icons.Custom.Uncategorized.WaterMelon, IsRotten = false, Place = "ObjBox" },
new DropItem { Icon = Icons.Custom.Uncategorized.Fish, IsRotten = true, Place = "ObjBox" }
};
private PointF? tempPoint { get; set; }
private string? imageUrl { get; set; }
private double realDistance { get; set; }
[Inject] private ScaleManager scaleManager { get; set; }
private IBrowserFile? iBrowserFile;
private int imageWidth;
private int imageHeight;
/// <summary>
/// Indicates whether this is the first time the component is rendering.
/// Initially set to true, it will be set to false after the first render.
/// </summary>
private bool isFirstRender = true;
/// <summary>
/// A list of image upload services used to manage uploaded images.
/// </summary>
private List<UploadedImageService> imageUpload = new();
private async Task onImageClick(MouseEventArgs e)
{
try
{
if (selectedPoints.Count < 2)
{
selectedPoints.Add(new PointF((float)e.OffsetX, (float)e.OffsetY));
}
else
{
selectedPoints.RemoveAt(0);
selectedPoints.Add(new PointF((float)e.OffsetX, (float)e.OffsetY));
}
if (selectedPoints.Count == 2)
{
// Calculate the pixel distance between the two points
var pixelDistance = calculatePixelDistance(selectedPoints[0], selectedPoints[1]);
// Output the points for debugging purposes
await JsRuntime.InvokeVoidAsync("console.log",
$"Point 1: {selectedPoints[0]}, Point 2: {selectedPoints[1]}, Distance: {pixelDistance}px");
}
objectPoints.Add(new PointF((float)e.OffsetX, (float)e.OffsetY));
StateHasChanged();
}
catch (Exception ex)
{
await JsRuntime.InvokeVoidAsync("console.error", $"Error: {ex.Message}");
}
}
private double calculatePixelDistance(PointF point1, PointF point2)
{
return Math.Sqrt(Math.Pow(point2.X - point1.X, 2) + Math.Pow(point2.Y - point1.Y, 2));
}
private void calculateScale()
{
if (selectedPoints.Count == 2 && realDistance > 0)
{
var pixelDistance = calculatePixelDistance(selectedPoints[0], selectedPoints[1]);
scaleManager.scale = pixelDistance / realDistance;
}
}
private async Task onFileSelected(IBrowserFile file)
{
iBrowserFile = file;
var buffer = new byte[iBrowserFile.Size];
await iBrowserFile.OpenReadStream().ReadAsync(buffer);
// Convert the uploaded image to a base64 string
imageUrl = $"data:image/png;base64,{Convert.ToBase64String(buffer)}";
}
private void OnMouseMove(MouseEventArgs e)
{
if (selectedPoints.Count == 1)
{
tempPoint = new PointF((float)e.OffsetX, (float)e.OffsetY);
StateHasChanged();
}
}
private void Reset()
{
foreach (var item in _items)
{
item.Place = "ObjBox";
item.IsPicked = false;
}
}
private void ItemUpdated(MudItemDropInfo<DropItem> dropItem)
{
dropItem.Item.IsPicked = true;
dropItem.Item.Place = dropItem.DropzoneIdentifier;
}
#endregion
}
public class DropItem
{
#region Public
public string Icon { get; init; }
public bool IsRotten { get; set; }
public bool IsPicked { get; set; }
public string Place { get; set; }
#endregion
}
Why are the objects appearing underneath the image rather than directly on it?
How can I ensure that the objects are correctly positioned on the image?
I would greatly appreciate any help or guidance on how to resolve this issue. Thanks in advance!
Upvotes: 0
Views: 49