我想弄清楚如何在GTK+3 sharp中绘制钢琴键。
在Visual Studio中创建它的最佳方法是什么?
感谢我不是很确定你需要什么,但因为我有一个键盘的实现为我的C项目,它可能会帮助你一点!我将在这里发布代码和我的解释!
这个实现为n个八度绘制一个键盘(该参数可以通过应用程序左侧的GtkScale指定),当钢琴上的一个键被按下时,它将以灰色突出显示。
如果有不懂的地方请随时提问!
概论
- 允许您在gtk上绘制的小部件称为GtkDrawingArea
- 要在gtk上绘制任何东西,必须使用cairo图书馆
- 要检测任何用户事件(例如鼠标按键),你需要设置一个GtkEventBox. GtkEventBox的子节点是GtkDrawingArea, GtkEventBox必须放在子节点之上,这样我们才能处理用户事件。 为了构建应用程序的Widget树,我们将使用Glade来加快这个过程。
方法
我是这样处理这个问题的!
-
绘制键盘让我们看一下钢琴键盘的一个八度,我们可以清楚地看到有4种不同类型的键:右键 (Do,Fa), 中键 (Re,Sol,La), 左键 (Mi,Si)和黑键。所以我们需要能够绘制所有这四种类型,如果我们想在按下时用不同的颜色重新绘制特定的键。
-
跟踪用户光标我已经说过,我们将使用GtkEventBox对于这个目的,但是一旦我们得到光标的(x,y)坐标,我们如何知道该坐标对应于哪个键区域呢?这里的问题是,只有黑键可以被看作是一个简单的矩形,其他键实际上是由两个组成的. 所以我们需要一个通用的函数来验证我们是否点击了一个特定的矩形区域!
代码
记住前面所有的想法,让我们来看看实际的代码!
空地 这是钢琴的。glade,让我们快速看一下,了解一下它是做什么的。
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.24"/>
<object class="GtkAdjustment" id="adjustment1">
<property name="lower">1</property>
<property name="upper">7</property>
<property name="value">1</property>
<property name="step-increment">1</property>
<property name="page-increment">10</property>
</object>
<object class="GtkWindow" id="wind">
<property name="can-focus">False</property>
<property name="default-width">480</property>
<property name="default-height">320</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkScale" id="octaves">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="orientation">vertical</property>
<property name="adjustment">adjustment1</property>
<property name="inverted">True</property>
<property name="round-digits">1</property>
<property name="digits">0</property>
<property name="value-pos">bottom</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEventBox" id="event_box">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="above-child">True</property>
<child>
<object class="GtkDrawingArea" id="da">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
首先可以看到GtkAdjustment对象,这将用于GtkScale这样你就可以选择你的钢琴有多少个八度。有一个主的GtkWindowGtkBoxGtkScale和GtkEventBox. 如前所述,的子GtkEventBox是 GtkDrawingArea
现在我们来讨论一下代码
#include <stdlib.h>
#include <stdio.h>
#include <gtk/gtk.h>
int x;
int y;
int octave_number;
//returns 1 is the (currentx,current_y) is in the rectangle else 0
int is_in_rectangle(int current_x, int current_y, int rect_top_x, int rect_top_y, int rect_width, int rect_height)
{
return (current_x < rect_top_x + rect_width && current_y < rect_top_y + rect_height && current_x > rect_top_x && current_y > rect_top_y) ? 1 : 0;
}
//Sets the new octave value
static gboolean
on_scale_change(GtkWidget *a_scale, __attribute_maybe_unused__ gpointer user_data)
{
int new_size = gtk_range_get_value(GTK_RANGE(a_scale));
// g_print("id: %dn", id);
octave_number = new_size;
return G_SOURCE_REMOVE;
}
//Gets the current event and sets the x,y possition
static gboolean
current_key_click(GtkWidget *event_box, __attribute_maybe_unused__ gpointer user_data)
{
GdkEvent *event = gtk_get_current_event();
GdkDisplay *display = gdk_display_get_default();
GdkSeat *seat = gdk_display_get_default_seat(display);
GdkDevice *device = gdk_seat_get_pointer(seat);
if (gdk_event_get_event_type(event) == GDK_BUTTON_PRESS)
{
gdk_window_get_device_position(gtk_widget_get_window(GTK_WIDGET(event_box)), device, &x, &y, NULL);
}
if (gdk_event_get_event_type(event) == GDK_BUTTON_RELEASE)
{
x = -1;
y = -1;
}
gdk_event_free(event);
return G_SOURCE_REMOVE;
}
// General axes set_up
void set_up_axes(GdkWindow *window, GdkRectangle *da, cairo_t *cr, gdouble *clip_x1, gdouble *clip_y1, gdouble *clip_x2, gdouble *clip_y2, gdouble *dx, gdouble *dy)
{
gdk_window_get_geometry(window, &da->x, &da->y, &da->width, &da->height);
//Draw white background
cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
cairo_paint(cr);
cairo_device_to_user_distance(cr, dx, dy);
cairo_clip_extents(cr, clip_x1, clip_y1, clip_x2, clip_y2);
cairo_set_line_width(cr, *dx);
}
// Draws all the lines in one octave
static gboolean
on_draw_key_lines(cairo_t *cr, int drawing_area_width, int drawing_area_height, int num_octaves)
{
int num_keys = 7 * num_octaves;
for (int o = 0; o < num_octaves; o++)
{
for (size_t j = 0; j <= 7; j++)
{
int i= j+ o*7;
if (j == 7 || j ==3)
{
cairo_line_to(cr, drawing_area_width * i / num_keys, 0);
}
else
{
cairo_line_to(cr, drawing_area_width * i / num_keys, drawing_area_height * 3 / 5);
}
cairo_line_to(cr, drawing_area_width * i / num_keys, drawing_area_height);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_stroke(cr);
}
}
return G_SOURCE_REMOVE;
}
// Draws one black key
static gboolean
on_draw_black_key(cairo_t *cr, int drawing_area_width, int drawing_area_height, int num_keys, int j)
{
cairo_set_source_rgb(cr, 0, 0, 0);
int top_left_x = drawing_area_width * j / (num_keys * 4);
int top_right_x = drawing_area_width * (2 + j) / (num_keys * 4);
int bot_right_y = drawing_area_height * 3 / 5;
cairo_line_to(cr, top_left_x, 0);
cairo_line_to(cr, top_right_x, 0);
cairo_line_to(cr, top_right_x, bot_right_y);
cairo_line_to(cr, top_left_x, bot_right_y);
cairo_line_to(cr, top_left_x, 0);
if (is_in_rectangle(x, y, top_left_x, 0, top_right_x - top_left_x, bot_right_y))
{
cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
}
cairo_fill(cr);
return G_SOURCE_REMOVE;
}
// Draw all the balck keys in one octave
static gboolean
on_draw_black_keys(cairo_t *cr, int drawing_area_width, int drawing_area_height, int num_octaves)
{
int num_keys = 7 * num_octaves;
for (int o = 0; o < num_octaves; o++)
{
for (size_t i = 3; i < 28; i += 4)
{
int j = i + o * 28;
if (i != 11 && i != 27)
{
on_draw_black_key(cr, drawing_area_width, drawing_area_height, num_keys, j);
}
}
}
return G_SOURCE_REMOVE;
}
//Draws one left type white key
static gboolean
on_draw_left_type_white_key(cairo_t *cr, int drawing_area_width, int drawing_area_height, int num_keys, int j)
{
// Default color if not pressed
cairo_set_source_rgb(cr, 1, 1, 1);
// The origin from which the tracing starts
int origin = drawing_area_width / (num_keys / 7) * j / 7;
// parameters of the top rectangle
int top_rect_width = drawing_area_width * 3 / (num_keys * 4);
int top_rect_height = drawing_area_height * 3 / 5;
// parametes of the bottom rectangle
int bot_rect_width = drawing_area_width / num_keys;
int bot_rect_height = drawing_area_height * 2 / 5;
// Draw the top part
cairo_line_to(cr, origin, 0);
cairo_line_to(cr, top_rect_width + origin, 0);
cairo_line_to(cr, top_rect_width + origin, top_rect_height);
// Check if the key is pressed on the top part
if (is_in_rectangle(x, y, origin, 0, top_rect_width, top_rect_height))
{
cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
}
// Draw the bottom part
cairo_line_to(cr, bot_rect_width + origin, top_rect_height);
cairo_line_to(cr, bot_rect_width + origin, drawing_area_height);
cairo_line_to(cr, origin, drawing_area_height);
cairo_line_to(cr, origin, 0);
// Check if the key is pressed on the bottom part
if (is_in_rectangle(x, y, origin, top_rect_height, bot_rect_width, bot_rect_height))
{
cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
}
// Draw the rectangle
cairo_fill(cr);
return G_SOURCE_REMOVE;
}
//Draws all the white keys
static gboolean
on_draw_left_type_white_keys(cairo_t *cr, int drawing_area_width, int drawing_area_height, int num_octaves)
{
int num_keys = 7 * num_octaves;
for (int o = 0; o < num_octaves; o++)
{
on_draw_left_type_white_key(cr, drawing_area_width, drawing_area_height, num_keys, 0 + o * 7);
on_draw_left_type_white_key(cr, drawing_area_width, drawing_area_height, num_keys, 3 + o * 7);
}
return G_SOURCE_REMOVE;
}
//Draws one center type white key
static gboolean
on_draw_center_type_white_key(cairo_t *cr, int drawing_area_width, int drawing_area_height, int num_keys, int j)
{
// Default color if not pressed
cairo_set_source_rgb(cr, 1, 1, 1);
// The tracing origin
int origin = drawing_area_width / (num_keys / 7) * j / 7 + drawing_area_width / (num_keys * 4);
// Top rectangle parameters
int top_rect_width = drawing_area_width / (num_keys * 2);
int top_rect_height = drawing_area_height * 3 / 5;
// parametes of the bottom rectangle
int bot_rect_width = origin - drawing_area_width / (num_keys * 4);
int bot_rect_height = drawing_area_height * 2 / 5;
// Trace the top part
cairo_line_to(cr, origin, 0);
cairo_line_to(cr, origin + top_rect_width, 0);
cairo_line_to(cr, origin + top_rect_width, top_rect_height);
// Check if the key is pressed on the top part
if (is_in_rectangle(x, y, origin, 0, top_rect_width, top_rect_height))
{
cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
}
// Trace the bottom part
cairo_line_to(cr, drawing_area_width * 3 / (num_keys * 4) + origin, top_rect_height);
cairo_line_to(cr, drawing_area_width * 3 / (num_keys * 4) + origin, drawing_area_height);
cairo_line_to(cr, bot_rect_width, drawing_area_height);
cairo_line_to(cr, bot_rect_width, top_rect_height);
cairo_line_to(cr, origin, top_rect_height);
cairo_line_to(cr, origin, 0);
// Check if the key is pressed on the bottom part
if (is_in_rectangle(x, y, bot_rect_width, top_rect_height, drawing_area_width / num_keys, bot_rect_height))
{
cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
}
cairo_fill(cr);
return G_SOURCE_REMOVE;
}
//Draws all center type white keys in an octave
static gboolean
on_draw_center_type_white_keys(cairo_t *cr, int drawing_area_width, int drawing_area_height, int num_octaves)
{
int num_keys = 7 * num_octaves;
for (int o = 0; o < num_octaves; o++)
{
on_draw_center_type_white_key(cr, drawing_area_width, drawing_area_height, num_keys, 1 + o * 7);
on_draw_center_type_white_key(cr, drawing_area_width, drawing_area_height, num_keys, 4 + o * 7);
on_draw_center_type_white_key(cr, drawing_area_width, drawing_area_height, num_keys, 5 + o * 7);
}
return G_SOURCE_REMOVE;
}
//Draws one right type white key
static gboolean
on_draw_right_type_white_key(cairo_t *cr, int drawing_area_width, int drawing_area_height, int num_keys, int j)
{
//Default color white
cairo_set_source_rgb(cr, 1, 1, 1);
//The origin of tracing
int origin = drawing_area_width / (num_keys / 7) * j / 7;
// parameters of the top rectangle
int top_rect_width = drawing_area_width * 3 / (num_keys * 4);
int top_rect_height = drawing_area_height * 3 / 5;
// parametes of the bottom rectangle
int bot_rect_width = drawing_area_width / num_keys;
int bot_rect_height = drawing_area_height * 2 / 5;
cairo_line_to(cr, origin, 0);
cairo_line_to(cr, origin, drawing_area_height);
cairo_line_to(cr, origin - bot_rect_width, drawing_area_height);
cairo_line_to(cr, origin - bot_rect_width, top_rect_height);
cairo_line_to(cr, origin - top_rect_width, top_rect_height);
cairo_line_to(cr, origin - top_rect_width, 0);
cairo_line_to(cr, origin, 0);
// Check if the key is pressed on the top part
if (is_in_rectangle(x, y, origin - top_rect_width, 0, top_rect_width, top_rect_height))
{
cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
}
// Check if the key is pressed on the bottom part
if (is_in_rectangle(x, y, origin - bot_rect_width , top_rect_height, bot_rect_width, bot_rect_height))
{
cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
}
cairo_fill(cr);
return G_SOURCE_REMOVE;
}
//Draws all right type white keys
static gboolean
on_draw_right_type_white_keys(cairo_t *cr, int drawing_area_width, int drawing_area_height, int num_octaves)
{
int num_keys = 7 * num_octaves;
for (int o = 0; o < num_octaves; o++)
{
on_draw_right_type_white_key(cr, drawing_area_width, drawing_area_height, num_keys, 3 + o * 7);
on_draw_right_type_white_key(cr, drawing_area_width, drawing_area_height, num_keys, 7 + o * 7);
}
return G_SOURCE_REMOVE;
}
//Draws the full keyboard
static gboolean
on_draw_full_keyboard(cairo_t *cr, int drawing_area_width, int drawing_area_height, int num_octaves)
{
// Draws all left type keys
on_draw_left_type_white_keys(cr, drawing_area_width, drawing_area_height, num_octaves);
// Draws all white type keys
on_draw_right_type_white_keys(cr, drawing_area_width, drawing_area_height, num_octaves);
// Draws all white center keys
on_draw_center_type_white_keys(cr, drawing_area_width, drawing_area_height, num_octaves);
// Draw the black keys
on_draw_black_keys(cr, drawing_area_width, drawing_area_height, num_octaves);
// Draw the lines for the keys
on_draw_key_lines(cr, drawing_area_width, drawing_area_height, num_octaves);
return G_SOURCE_REMOVE;
}
// Dynamically draws the signal
static gboolean
on_draw_signal(GtkWidget *widget, cairo_t *cr, __attribute_maybe_unused__ gpointer user_data)
{
GdkRectangle da; /* GtkDrawingArea size */
gdouble dx = 2.0, dy = 2.0; /* Pixels between each point */
gdouble clip_x1 = 0.0, clip_y1 = 0.0, clip_x2 = 0.0, clip_y2 = 0.0;
GdkWindow *window = gtk_widget_get_window(widget);
int drawing_area_width = gtk_widget_get_allocated_width(widget);
int drawing_area_height = gtk_widget_get_allocated_height(widget);
set_up_axes(window, &da, cr, &clip_x1, &clip_x2, &clip_y1, &clip_y2, &dx, &dy);
on_draw_full_keyboard(cr,drawing_area_width,drawing_area_height,octave_number);
gtk_widget_queue_draw_area(widget, 0, 0, drawing_area_width, drawing_area_height);
return G_SOURCE_REMOVE;
}
int main()
{
gtk_init(NULL, NULL);
x = y = -1;
octave_number = 1;
GtkBuilder *builder = gtk_builder_new();
GError *error = NULL;
if (gtk_builder_add_from_file(builder, "piano.glade", &error) == 0)
{
g_printerr("Error loading file: %sn", error->message);
g_clear_error(&error);
return 1;
}
GtkWindow *window = GTK_WINDOW(gtk_builder_get_object(builder, "wind"));
GtkDrawingArea *da = GTK_DRAWING_AREA(gtk_builder_get_object(builder, "da"));
GtkEventBox *event_box = GTK_EVENT_BOX(gtk_builder_get_object(builder, "event_box"));
GtkScale *scale = GTK_SCALE(gtk_builder_get_object(builder, "octaves"));
g_signal_connect(G_OBJECT(window), "destroy", gtk_main_quit, NULL);
g_signal_connect(G_OBJECT(da), "draw", G_CALLBACK(on_draw_signal), NULL);
g_signal_connect(G_OBJECT(event_box), "event", G_CALLBACK(current_key_click), NULL);
g_signal_connect(G_OBJECT(scale), "value_changed", G_CALLBACK(on_scale_change), NULL);
gtk_widget_show_all(GTK_WIDGET(window));
gtk_main();
return 0;
}
代码中的注释是不言自明的,但是如果您对某个特定函数有任何疑问,请不要犹豫!
这是我用来编译代码的!
gcc -Wall -Wextra `pkg-config --cflags gtk+-3.0` -g -fsanitize=address main.c -o piano `pkg-config --libs gtk+-3.0`
我希望这是有帮助的!最好的祝福,