content dialog with points
Eduardo Gomez Romero
1,335
Reputation points
This is what is happening
I have an application, where you can draw on a line on a Canvas, and convert those lines in STL, and then save it on your computer
private const string NanoFlowFolder = "NanoFlow";
private const int gridSpacing = 20; // Spacing between grid lines in pixels
private readonly int _canvasSize = 378; // Size of the canvas in pixels
private bool _areGridMarginsAdded = false;
private readonly List<Line> _gridMarginLines = []; // To track grid lines
private readonly List<TextBlock> _gridTextBlocks = [];
private Grid? _rootContainer;
private Canvas? _canvas;
private PointModel? _previousPoint;
public readonly ObservableCollection<PointModel> Points = [];
public readonly ObservableCollection<LineModel> Lines = [];
public readonly List<Tuple<PointModel, PointModel>> LineData = [];
public readonly List<PointModel> _selectedPoints = [];
public void SetRootContainer(object sender, RoutedEventArgs args) {
_rootContainer = sender as Grid;
}
[RelayCommand]
async Task SaveToSTLAsync() {
// Create the dialog
var fileNameDialog = new ContentDialog {
Title = "Save Design",
Content = new StackPanel {
Children =
{
new TextBlock { Text = "Enter a name for your design:" },
new TextBox { PlaceholderText = "MyDesign.stl", Name = "FileNameTextBox" }
}
},
PrimaryButtonText = "Save",
CloseButtonText = "Cancel",
XamlRoot = _rootContainer?.XamlRoot
};
// Show the dialog
var result = await fileNameDialog.ShowAsync();
if(result == ContentDialogResult.Primary) {
// Retrieve the TextBox value
var stackPanel = (StackPanel)fileNameDialog.Content;
var textBox = stackPanel.Children.OfType<TextBox>().First();
var fileName = textBox.Text;
// Validate the file name
if(string.IsNullOrWhiteSpace(fileName)) {
// Use a default file name if none is provided
fileName = "MyDesign.stl";
}
else if(!fileName.EndsWith(".stl", StringComparison.OrdinalIgnoreCase)) {
// Append the ".stl" extension if it's missing
fileName += ".stl";
}
// Generate the STL file
GetLineData(); // Ensure LineData is updated
ExportSTL(fileName, 5.0);
Console.WriteLine($"STL file '{fileName}' generated successfully.");
}
}
[RelayCommand]
void CreateNewDesign() {
DrawCanvas();
if(_canvas != null) {
_canvas!.PointerPressed += Canvas_PointerPressed;
_canvas.PointerMoved += Canvas_PointerMoved;
_canvas.PointerReleased += Canvas_PointerReleased;
}
Points.Clear();
Lines.Clear();
LineData.Clear();
}
[RelayCommand]
async Task GuidedProcessAsync() {
Points.Clear();
_selectedPoints.Clear();
DrawCanvas();
for(int i = 0; i < _canvasSize; i += gridSpacing) {
for(int j = 0; j < _canvasSize; j += gridSpacing) {
var point = new PointModel(i, j, DrawPoint);
Points.Add(point);
}
}
// Open the dialog and pass the ViewModel
var dialog = new GuidedProcessDialog(this) {
XamlRoot = _rootContainer?.XamlRoot
};
await dialog.ShowAsync();
}
[RelayCommand]
void AddMargin() {
if(_canvas == null) {
return;
}
if(_areGridMarginsAdded) {
RemoveGridMarginsAndTextBlocks();
_areGridMarginsAdded = false;
}
else {
DrawGridMarginsAndTextBlocks();
_areGridMarginsAdded = true;
}
}
private void RemoveGridMarginsAndTextBlocks() {
var allItems = _gridMarginLines.Cast<UIElement>()
.Concat(_gridTextBlocks.Cast<UIElement>())
.ToList();
foreach(var element in allItems) {
_canvas!.Children.Remove(element);
}
// Clear both lists afterward
_gridMarginLines.Clear();
_gridTextBlocks.Clear();
}
public List<Tuple<PointModel, PointModel>> GetLineData() {
LineData.Clear();
foreach(var line in Lines) {
LineData.Add(new Tuple<PointModel, PointModel>(line.Start, line.End));
}
return LineData;
}
private static PointModel3D Create3DPoint(PointModel point, double z) => new(point.X, point.Y, z);
public List<Tuple<PointModel3D, PointModel3D, PointModel3D, PointModel3D>> Generate3DLines(double thickness) {
List<Tuple<PointModel3D, PointModel3D, PointModel3D, PointModel3D>> extrudedLines = [];
foreach(var line in LineData) {
var startBase = Create3DPoint(line.Item1, 0);
var endBase = Create3DPoint(line.Item2, 0);
var startTop = Create3DPoint(line.Item1, thickness);
var endTop = Create3DPoint(line.Item2, thickness);
extrudedLines.Add(new Tuple<PointModel3D, PointModel3D, PointModel3D, PointModel3D>(
startBase, endBase, endTop, startTop
));
}
return extrudedLines;
}
public void ExportSTL(string filePath, double thickness) {
GetLineData();
var extrudedLines = Generate3DLines(thickness);
// Validate extrudedLines before proceeding
if(extrudedLines.Count == 0) {
throw new InvalidOperationException("No 3D geometry created. Check the logic in Generate3DLines.");
}
var desinsFolder = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), NanoFlowFolder);
if(!Directory.Exists(desinsFolder)) {
Directory.CreateDirectory(desinsFolder);
}
var stlFilePath = Path.Combine(desinsFolder, filePath);
var stlContent = new StringWriter(CultureInfo.InvariantCulture);
stlContent.WriteLine("solid Model");
foreach(var rectangle in extrudedLines) {
var p1 = rectangle.Item1;
var p2 = rectangle.Item2;
var p3 = rectangle.Item3;
var p4 = rectangle.Item4;
AddRectangleToSTL(stlContent, p1, p2, p3, p4);
}
stlContent.WriteLine("endsolid Model");
File.WriteAllText(stlFilePath, stlContent.ToString());
}
private static void AddRectangleToSTL(StringWriter stl, PointModel3D p1, PointModel3D p2,
PointModel3D p3, PointModel3D p4) {
// Define two triangles for the rectangle
AddTriangleToStl(stl, p1, p2, p3); // Triangle 1
AddTriangleToStl(stl, p1, p3, p4); // Triangle 2
}
private static void AddTriangleToStl(StringWriter stl, PointModel3D v1, PointModel3D v2, PointModel3D v3) {
stl.WriteLine(" facet normal 0 0 0"); // Normal vector (set to 0 for simplicity)
stl.WriteLine(" outer loop");
stl.WriteLine($" vertex {v1.X} {v1.Y} {v1.Z}");
stl.WriteLine($" vertex {v2.X} {v2.Y} {v2.Z}");
stl.WriteLine($" vertex {v3.X} {v3.Y} {v3.Z}");
stl.WriteLine(" endloop");
stl.WriteLine(" endfacet");
}
#region Drawing Methods
private void DrawCanvas() {
_canvas = new Canvas {
Background = new SolidColorBrush(Color.FromArgb(255, 0, 128, 0)),
Width = _canvasSize,
Height = _canvasSize,
};
if(_rootContainer != null) {
_rootContainer.Children.Add(_canvas);
Grid.SetRow(_canvas, 1);
}
}
private void DrawGridMarginsAndTextBlocks() {
if(_canvas == null) {
return;
}
for(int i = 0; i < _canvas!.Width; i += gridSpacing) {
for(int j = 0; j < _canvas.Height; j += gridSpacing) {
// Draw vertical and horizontal lines crossing at intersections
if(i == 0) {
var horizontalLine = new Line {
X1 = 0,
Y1 = j,
X2 = _canvas.Width,
Y2 = j,
Stroke = new SolidColorBrush(Colors.Gray),
StrokeThickness = 1
};
_canvas.Children.Add(horizontalLine);
_gridMarginLines.Add(horizontalLine);
}
if(j == 0) {
var verticalLine = new Line {
X1 = i,
Y1 = 0,
X2 = i,
Y2 = _canvas.Height,
Stroke = new SolidColorBrush(Colors.Gray),
StrokeThickness = 1
};
_canvas.Children.Add(verticalLine);
_gridMarginLines.Add(verticalLine);
}
// Add a TextBlock at each intersection
var textBlock = new TextBlock {
Text = $"({i / gridSpacing},{j / gridSpacing})",
Foreground = new SolidColorBrush(Colors.Black),
FontSize = 6
};
Canvas.SetLeft(textBlock, i + 2); // Position slightly offset for visibility
Canvas.SetTop(textBlock, j + 2);
_canvas.Children.Add(textBlock);
_gridTextBlocks.Add(textBlock);
}
}
}
private void DrawLine(PointModel startPoint, PointModel endPoint) {
var line = new Line {
X1 = startPoint.X,
Y1 = startPoint.Y,
X2 = endPoint.X,
Y2 = endPoint.Y,
Stroke = new SolidColorBrush(Colors.White),
StrokeThickness = 2,
};
_canvas!.Children.Add(line);
Lines.Add(new LineModel(startPoint, endPoint));
}
private void DrawPoint(PointModel point) {
if(_selectedPoints.Contains(point)) {
var toRemove = _canvas!.Children.OfType<Ellipse>().FirstOrDefault(e => e.Tag == point);
if(toRemove != null) {
_canvas.Children.Remove(toRemove);
}
// Remove the point from the selected points list
_selectedPoints.Remove(point);
}
else {
var ellipse = new Ellipse {
Width = 4,
Height = 4,
Fill = new SolidColorBrush(Colors.Red)
};
Canvas.SetLeft(ellipse, point.X - 2);
Canvas.SetTop(ellipse, point.Y - 2);
_canvas!.Children.Add(ellipse);
// Connect with the previous point if one exists
if(_selectedPoints.Count > 0) {
var lastPoint = _selectedPoints[^1];
DrawLine(lastPoint, point);
}
_selectedPoints.Add(point);
}
}
#endregion
#region UI Event Handlers
private void Canvas_PointerPressed(object sender, PointerRoutedEventArgs e) {
var position = e.GetCurrentPoint(_canvas).Position;
// Create the PointModel with the action(s) required
var currentPoint = new PointModel((int)position.X, (int)position.Y, DrawPoint);
// Set the previous point for line drawing
_previousPoint = currentPoint;
// Trigger the action to draw the point
DrawPoint(currentPoint);
}
private void Canvas_PointerMoved(object sender, PointerRoutedEventArgs e) {
if(_previousPoint != null && e.Pointer.IsInContact) {
var position = e.GetCurrentPoint(_canvas).Position;
// Create the PointModel dynamically with the actions
var currentPoint = new PointModel((int)position.X, (int)position.Y, DrawPoint);
// Draw a line between the points
DrawLine(_previousPoint, currentPoint);
// Update the previous point
_previousPoint = currentPoint;
}
}
private void Canvas_PointerReleased(object sender, PointerRoutedEventArgs e) {
// Reset the previous point to stop drawing
_previousPoint = null;
}
#endregion
}
}
These are the models to make this happen
public partial class PointModel3D(double x, double y, double z) {
public double X { get; set; } = x;
public double Y { get; set; } = y;
public double Z { get; set; } = z;
}
public partial class PointModel(int x, int y, Action<PointModel> selectAction) : ObservableObject {
public int X { get; set; } = x;
public int Y { get; set; } = y;
private const int GridSpace = 20;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(DisplayText))]
private bool isSelected;
private readonly Action<PointModel> selectAction = selectAction;
public string DisplayText => $"Point ({X / GridSpace}, {Y / GridSpace})";
partial void OnIsSelectedChanged(bool value) {
selectAction?.Invoke(this);
}
}
}
public partial class LineModel(PointModel start, PointModel end) : ObservableObject {
[ObservableProperty]
private PointModel start = start;
[ObservableProperty]
private PointModel end = end;
}
}
What is happening?
I created a dialog, canvas and a grid view, that should contain all the points. When the user checks a textbox, it should draw the point and if I uncheck it should delete the point (This is a feature, to let the user see in Realtime what he is drawing)
guidedProcessDialog
Title="Guided Process"
Width="800"
Height="600"
PrimaryButtonText="Close">
<Grid>
<!-- Define Columns -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<!-- Left Side: GridView -->
<ColumnDefinition Width="3*" />
<!-- Right Side: Canvas -->
</Grid.ColumnDefinitions>
<!-- GridView for Point Selection -->
<GridView
Grid.Column="0"
ItemsSource="{x:Bind ViewModel.Points}"
SelectionMode="None">
<GridView.ItemTemplate>
<DataTemplate x:DataType="local:PointModel">
<CheckBox
Content="{x:Bind DisplayText}"
IsChecked="{x:Bind IsSelected, Mode=TwoWay}" />
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
<!-- Canvas for Real-Time Drawing -->
<Canvas
x:Name="DrawingCanvas"
Grid.Column="1"
Width="378"
Height="378"
Background="#FF008000" />
</Grid>
</ContentDialog>
code behind
public sealed partial class GuidedProcessDialog : ContentDialog {
MainViewModel? ViewModel { get; set; }
public GuidedProcessDialog(MainViewModel ViewModel) {
InitializeComponent();
DataContext = ViewModel;
}
}
Dialog
If doesn't show, how can I see if I can draw by checking the checkboxes?
Sign in to answer