3
社区成员
发帖
与我相关
我的任务
分享<Window x:Class="CandyCAD.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CandyCAD"
Title="CandyCAD - 3D Viewer"
Height="600" Width="900"
Loaded="Window_Loaded"
Closing="Window_Closing">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 左侧控制面板 -->
<Border Grid.Column="0" Background="#2A2A2A" BorderBrush="#444" BorderThickness="0,0,1,0">
<StackPanel Margin="10">
<TextBlock Text="CandyCAD" FontSize="18" FontWeight="Bold" Foreground="#F0D84C"
HorizontalAlignment="Center" Margin="0,0,0,15"/>
<Border Background="#3A3A3A" CornerRadius="4" Padding="8" Margin="0,0,0,10">
<StackPanel>
<TextBlock Text="当前场景" Foreground="#CCC" FontWeight="Bold" Margin="0,0,0,5"/>
<TextBlock x:Name="SceneInfo" Text="立方体" Foreground="#888" FontSize="11"/>
<TextBlock x:Name="VertexInfo" Text="顶点: 36" Foreground="#888" FontSize="11"/>
<TextBlock x:Name="TriangleInfo" Text="三角形: 12" Foreground="#888" FontSize="11"/>
</StackPanel>
</Border>
<GroupBox Header="相机控制" Foreground="#CCC" Margin="0,0,0,10">
<StackPanel>
<Button x:Name="ResetCameraBtn" Content="重置相机" Height="30" Margin="0,3"
Click="ResetCamera_Click" Background="#4A6A8A" Foreground="White" BorderThickness="0"/>
<Button x:Name="FitViewBtn" Content="适应视图" Height="30" Margin="0,3"
Click="FitView_Click" Background="#4A6A8A" Foreground="White" BorderThickness="0"/>
</StackPanel>
</GroupBox>
<GroupBox Header="模型变换" Foreground="#CCC" Margin="0,0,0,10">
<StackPanel>
<Slider x:Name="RotateSlider" Minimum="0" Maximum="360" Value="45"
TickFrequency="45" TickPlacement="BottomRight"
ValueChanged="RotateSlider_Changed"/>
<TextBlock Text="旋转角度" Foreground="#AAA" FontSize="11" HorizontalAlignment="Center"/>
<Slider x:Name="ScaleSlider" Minimum="0.5" Maximum="2.0" Value="1.0"
TickFrequency="0.25" TickPlacement="BottomRight" Margin="0,10,0,0"
ValueChanged="ScaleSlider_Changed"/>
<TextBlock Text="缩放" Foreground="#AAA" FontSize="11" HorizontalAlignment="Center"/>
</StackPanel>
</GroupBox>
<GroupBox Header="渲染设置" Foreground="#CCC" Margin="0,0,0,10">
<StackPanel>
<CheckBox x:Name="WireframeCheckBox" Content="线框模式" Foreground="#DDD" Margin="0,3"
Checked="WireframeMode_Changed" Unchecked="WireframeMode_Changed"/>
<CheckBox x:Name="GridCheckBox" Content="显示网格" Foreground="#DDD" Margin="0,3"
IsChecked="True" Checked="GridMode_Changed" Unchecked="GridMode_Changed"/>
</StackPanel>
</GroupBox>
<TextBlock x:Name="InfoText" Text="鼠标拖拽旋转视图" Foreground="#888" FontSize="11"
HorizontalAlignment="Center" Margin="0,10,0,0"/>
<TextBlock x:Name="FpsText" Text="FPS: --" Foreground="#F0D84C" FontSize="12"
HorizontalAlignment="Center" Margin="0,10,0,0"/>
</StackPanel>
</Border>
<!-- 右侧渲染区域 -->
<Border Grid.Column="1" Background="#1E1E1E">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<!-- OpenGL 渲染视图 -->
<Border Grid.Column="1" Background="#1E1E1E" Margin="5">
<WindowsFormsHost x:Name="host">
<local:OpenGLControl x:Name="glView"
BackColor="#1E1E1E"
Dock="Fill"
MinimumSize="400,300" />
</WindowsFormsHost>
</Border>
<!-- 状态栏 -->
<StatusBar Grid.Row="1" Background="#2A2A2A">
<StatusBarItem>
<TextBlock x:Name="StatusText" Text="就绪" Foreground="#888"/>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock x:Name="CameraPosText" Text="相机: (0, 0, 5)" Foreground="#888"/>
</StatusBarItem>
<Separator/>
<StatusBarItem>
<TextBlock x:Name="MousePosText" Text="鼠标: (0, 0)" Foreground="#888"/>
</StatusBarItem>
</StatusBar>
</Grid>
</Border>
</Grid>
</Window>
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
namespace CandyCAD
{
public partial class MainWindow : Window
{
private int frameCount = 0;
private DateTime lastTime = DateTime.Now;
private System.Windows.Point lastMousePos;
private bool isMouseDown = false;
public MainWindow()
{
InitializeComponent();
// 在 glView 上直接订阅鼠标事件(WinForms 事件)
glView.MouseDown += OnGlViewMouseDown;
glView.MouseMove += OnGlViewMouseMove;
glView.MouseUp += OnGlViewMouseUp;
glView.MouseWheel += OnGlViewMouseWheel;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
CompositionTarget.Rendering += OnRendering;
if (StatusText != null)
StatusText.Text = "CandyCAD 已启动 | 左键旋转 | 右键平移 | 滚轮缩放";
}
private void OnRendering(object sender, EventArgs e)
{
if (glView != null)
{
RenderInterop.RenderFrame();
}
frameCount++;
var now = DateTime.Now;
if ((now - lastTime).TotalSeconds >= 1)
{
if (FpsText != null)
FpsText.Text = $"FPS: {frameCount}";
frameCount = 0;
lastTime = now;
}
RenderInterop.GetCameraPosition(out float x, out float y, out float z);
if (CameraPosText != null)
CameraPosText.Text = $"相机: ({x:F1}, {y:F1}, {z:F1})";
}
// ===== WinForms 鼠标事件(直接使用 glView)=====
private void OnGlViewMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
lastMousePos = new System.Windows.Point(e.X, e.Y);
isMouseDown = true;
glView.Capture = true; // WinForms 的 Capture 属性
if (StatusText != null)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
StatusText.Text = "旋转视图 (左键拖拽)";
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
StatusText.Text = "平移视图 (右键拖拽)";
}
}
private void OnGlViewMouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
if (!isMouseDown) return;
double dx = e.X - lastMousePos.X;
double dy = e.Y - lastMousePos.Y;
if (MousePosText != null)
MousePosText.Text = $"鼠标: ({e.X}, {e.Y})";
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
RenderInterop.ProcessMouseMovement((float)dx, (float)dy);
}
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
RenderInterop.ProcessPan((float)dx, (float)dy);
}
lastMousePos = new System.Windows.Point(e.X, e.Y);
}
private void OnGlViewMouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
isMouseDown = false;
glView.Capture = false; // WinForms 的 Capture 属性
if (StatusText != null)
StatusText.Text = "就绪 | 左键旋转 | 右键平移 | 滚轮缩放";
}
private void OnGlViewMouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
{
float delta = e.Delta > 0 ? 1.0f : -1.0f;
RenderInterop.ProcessMouseScroll(delta);
if (StatusText != null)
StatusText.Text = e.Delta > 0 ? "放大" : "缩小";
}
// ===== 按钮事件 =====
private void ResetCamera_Click(object sender, RoutedEventArgs e)
{
RenderInterop.ResetCamera();
if (StatusText != null)
StatusText.Text = "相机已重置";
}
private void FitView_Click(object sender, RoutedEventArgs e)
{
RenderInterop.FitView();
if (StatusText != null)
StatusText.Text = "适应视图";
}
private void RotateSlider_Changed(object sender, RoutedPropertyChangedEventArgs<double> e)
{
float angle = (float)e.NewValue;
RenderInterop.SetModelRotation(angle, angle, 0);
if (InfoText != null)
InfoText.Text = $"旋转角度: {angle:F0}°";
}
private void ScaleSlider_Changed(object sender, RoutedPropertyChangedEventArgs<double> e)
{
float scale = (float)e.NewValue;
RenderInterop.SetModelScale(scale);
if (InfoText != null)
InfoText.Text = $"缩放: {scale:F2}";
}
private void WireframeMode_Changed(object sender, RoutedEventArgs e)
{
bool enabled = WireframeCheckBox.IsChecked == true;
RenderInterop.SetWireframeMode(enabled);
if (StatusText != null)
StatusText.Text = enabled ? "线框模式" : "实体模式";
}
private void GridMode_Changed(object sender, RoutedEventArgs e)
{
bool show = GridCheckBox.IsChecked == true;
RenderInterop.ShowGrid(show);
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
RenderInterop.Shutdown();
}
}
}
using System;
using System.Windows.Forms;
namespace CandyCAD
{
public class OpenGLControl : Control
{
public OpenGLControl()
{
SetStyle(ControlStyles.EnableNotifyMessage, true);
this.MouseWheel += (s, e) => { };
}
private bool isInitialized = false;
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
System.Diagnostics.Debug.WriteLine("=== OnHandleCreated called ===");
System.Diagnostics.Debug.WriteLine($"Handle: {this.Handle}, Width: {this.Width}, Height: {this.Height}");
if (!isInitialized && this.IsHandleCreated && this.Width > 0 && this.Height > 0)
{
System.Diagnostics.Debug.WriteLine("Calling RenderInterop.Initialize...");
bool result = RenderInterop.Initialize(this.Handle, this.Width, this.Height);
System.Diagnostics.Debug.WriteLine($"RenderInterop.Initialize returned: {result}");
if (!result)
throw new Exception("Failed to initialize OpenGL renderer");
isInitialized = true;
System.Diagnostics.Debug.WriteLine("OpenGL initialized successfully");
}
else
{
System.Diagnostics.Debug.WriteLine("Skipping initialization - conditions not met");
System.Diagnostics.Debug.WriteLine($"isInitialized: {isInitialized}");
System.Diagnostics.Debug.WriteLine($"IsHandleCreated: {this.IsHandleCreated}");
System.Diagnostics.Debug.WriteLine($"Width: {this.Width}, Height: {this.Height}");
}
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
if (isInitialized && this.Width > 0 && this.Height > 0)
{
RenderInterop.Resize(this.Width, this.Height);
}
}
protected override void OnPaint(PaintEventArgs e)
{
if (isInitialized)
RenderInterop.RenderFrame();
}
protected override void Dispose(bool disposing)
{
if (disposing && isInitialized)
RenderInterop.Shutdown();
base.Dispose(disposing);
}
}
}
using System;
using System.Runtime.InteropServices;
namespace CandyCAD
{
public static class RenderInterop
{
private const string DllName = "CandyRenderEngine.dll";
// 基础渲染
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern bool Initialize(IntPtr hwnd, int width, int height);
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void Shutdown();
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void RenderFrame();
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void Resize(int width, int height);
// 相机控制
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void ProcessMouseMovement(float xOffset, float yOffset);
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void ProcessMouseScroll(float yOffset);
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void ProcessPan(float xOffset, float yOffset);
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void ResetCamera();
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void FitView();
// 模型变换
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void SetModelRotation(float x, float y, float z);
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void SetModelScale(float scale);
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void SetModelPosition(float x, float y, float z);
// 渲染设置
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void SetWireframeMode(bool enabled);
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void ShowGrid(bool show);
// 获取信息
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern void GetCameraPosition(out float x, out float y, out float z);
// 添加到 RenderInterop.cs 中
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern bool GetWireframeMode();
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
public static extern bool IsGridVisible();
}
}