juce - 自定义组件之SpinBox
SpinBox.h
#pragma once
#include <JuceHeader.h>
class SpinBox : public juce::Component,
public juce::Button::Listener,
public juce::TextEditor::Listener
{
public:
// 构造函数:默认范围 0-100,步长 1,无单位
SpinBox(float minValue = 0, float maxValue = 100, float step = 1);
~SpinBox();
// 设置当前值
void setValue(float newValue);
// 获取当前值
float getValue() const;
//设置最小值
void setMinValue(float newValue);
//设置最大值
void setMaxValue(float newValue);
//设置步长
void setSteo(float step);
// 数值变化回调
//std::function<void(float)> onValueChanged;
void resized() override;
void buttonClicked(juce::Button* button) override;
// 输入框内容变化
void textEditorTextChanged(juce::TextEditor& editor) override;
// 新增监听器接口
class Listener {
public:
virtual ~Listener() = default;
virtual void ValueChanged(SpinBox* spinBox) = 0;
};
// 新增注册/注销监听器方法
void addListener(Listener* listener);
void removeListener(Listener* listener);
// 新增监听器列表
private:
juce::ListenerList<Listener> listeners;
juce::ScopedPointer<juce::DrawableImage> upImage;
juce::ScopedPointer<juce::DrawableImage> downImage;
juce::ScopedPointer<juce::DrawableImage> pressed_upImage;
juce::ScopedPointer<juce::DrawableImage> pressed_downImage;
juce::TextEditor valueEditor; // 数值输入框
juce::ScopedPointer<juce::DrawableButton> upButton; // 增加按钮(向上箭头)
juce::ScopedPointer<juce::DrawableButton> downButton; // 减少按钮(向下箭头)
float m_currentValue; // 当前值
float m_minValue; // 最小值
float m_maxValue; // 最大值
float m_step; // 步长
};
SpinBox.cpp
#include "SpinBox.h"
SpinBox::SpinBox(float minValue, float maxValue, float step)
{
m_minValue = minValue;
m_maxValue = maxValue;
m_currentValue = m_minValue;
m_step = step;
// 设置初始文本
//valueEditor.setText(juce::String(m_currentValue), juce::dontSendNotification);
valueEditor.setText(juce::String::formatted("%.2f", m_currentValue), juce::dontSendNotification);
// 设置文本对齐方式为水平和垂直居中
valueEditor.setJustification(juce::Justification::centred);
// 可选:设置字体大小(例如 16px)
//valueEditor.setFont(juce::Font(18.0f));
// 可选:移除边框,避免边框干扰居中效果
//valueEditor.setColour(juce::TextEditor::outlineColourId, juce::Colours::transparentBlack);
//valueEditor.setColour(juce::TextEditor::backgroundColourId, juce::Colours::transparentBlack);
valueEditor.addListener(this);
//先添加输入框后添加按钮,要不然会遮挡按钮
addAndMakeVisible(valueEditor);
upImage.reset(new juce::DrawableImage());
downImage.reset(new juce::DrawableImage());
pressed_upImage.reset(new juce::DrawableImage());
pressed_downImage.reset(new juce::DrawableImage());
// 加载二进制数据到Image对象
juce::Image upImageData = juce::ImageFileFormat::loadFrom(BinaryData::up_png, BinaryData::up_pngSize);
juce::Image downImageData = juce::ImageFileFormat::loadFrom(BinaryData::down_png, BinaryData::down_pngSize);
juce::Image pressed_upImageData = juce::ImageFileFormat::loadFrom(BinaryData::pressed_up_png, BinaryData::pressed_up_pngSize);
juce::Image pressed_downImageData = juce::ImageFileFormat::loadFrom(BinaryData::pressed_down_png, BinaryData::pressed_down_pngSize);
// 验证图像有效性
if (!upImageData.isValid() || !downImageData.isValid())
{
juce::AlertWindow::showMessageBoxAsync(juce::AlertWindow::WarningIcon, "error", "Unable to load image asset!");
jassertfalse;
}
DBG("upImage width: " << upImageData.getWidth() << ", height: " << upImageData.getHeight());
DBG("downImage width: " << downImageData.getWidth() << ", height: " << downImageData.getHeight());
// 设置图像
upImage->setImage(upImageData);
downImage->setImage(downImageData);
pressed_upImage->setImage(pressed_upImageData);
pressed_downImage->setImage(pressed_downImageData);
//upButton = new juce::DrawableButton("", juce::DrawableButton::ImageFitted); // 按钮模式:使用 ImageFitted 确保图标自动缩放适配按钮大小
upButton.reset(new juce::DrawableButton("", juce::DrawableButton::ImageRaw));
upButton->setImages(
upImage, // 正常状态
upImage, // 悬停状态
pressed_upImage, // 按下状态
nullptr, // 禁用状态
nullptr // 正常状态覆盖颜色
);
upButton->addListener(this);
upButton->setColour(juce::DrawableButton::backgroundColourId, juce::Colours::transparentBlack);
addAndMakeVisible(upButton);
//downButton = new juce::DrawableButton("", juce::DrawableButton::ImageFitted);
downButton.reset(new juce::DrawableButton("", juce::DrawableButton::ImageRaw));
// 设置按钮多状态图像
downButton->setImages(
downImage,
downImage,
pressed_downImage,
nullptr,
nullptr
);
downButton->addListener(this);// 注册按钮监听
downButton->setColour(juce::DrawableButton::backgroundColourId, juce::Colours::transparentBlack);// 按钮背景颜色
addAndMakeVisible(downButton);
}
SpinBox::~SpinBox()
{
}
float SpinBox::getValue() const
{
DBG("currentValue = " << m_currentValue);
return m_currentValue;
}
void SpinBox::setMinValue(float newValue)
{
m_minValue = newValue;
}
void SpinBox::setMaxValue(float newValue)
{
m_maxValue = newValue;
}
void SpinBox::setSteo(float step)
{
m_step = step;
}
void SpinBox::resized()
{
int w = getWidth();
int h = getHeight();
// 输入框占大部分宽度,留出右侧空间给按钮
valueEditor.setBounds(0, 0, w, h);
if (upButton && downButton)
{
// 按钮宽高与图片原始尺寸匹配
const int btnWidth = 16; // 假设图片原始宽度为 32px
const int btnHeight = 16; // 假设图片原始高度为 32px
upButton->setBounds(w - btnWidth, 0, btnWidth, btnHeight);
downButton->setBounds(w - btnWidth, h - btnHeight, btnWidth, btnHeight);
}
}
// 修改 setValue 方法,触发监听器
void SpinBox::setValue(float newValue)
{
newValue = juce::jlimit(m_minValue, m_maxValue, newValue);
if (m_currentValue != newValue)
{
m_currentValue = newValue;
valueEditor.setText(juce::String::formatted("%.2f", m_currentValue), juce::dontSendNotification);
listeners.call([this](Listener& listen) { listen.ValueChanged(this); }); // 触发回调
}
}
// 按钮点击处理函数
void SpinBox::buttonClicked(juce::Button* button)
{
if (button == upButton)
{
setValue(m_currentValue + m_step);
}
else if (button == downButton)
{
setValue(m_currentValue - m_step);
}
}
// 输入框内容变化逻辑
void SpinBox::textEditorTextChanged(juce::TextEditor& editor)
{
float newValue = valueEditor.getText().getFloatValue();
DBG("newValue = " << newValue);
setValue(newValue);
}
void SpinBox::addListener(Listener* listener)
{
listeners.add(listener);
}
void SpinBox::removeListener(Listener* listener)
{
listeners.remove(listener);
}
使用SpinBox组件
ExtenderComponent.h
#pragma once
#include <JuceHeader.h>
#include "SpinBox.h"
class ExtenderComponent :
public juce::Component,
public SpinBox::Listener
{
public:
ExtenderComponent();
~ExtenderComponent();
protected:
void resized() override;
void paint(juce::Graphics& g) override;
void ValueChanged(SpinBox* spinBox) override;
private:
juce::Label label_1;
juce::Label label_2;
juce::Label label_3;
juce::Label label_4;
SpinBox spin1;
SpinBox spin2;
SpinBox spin3;
SpinBox spin4;
};
ExtenderComponent.cpp
#include "ExtenderComponent.h"
ExtenderComponent::ExtenderComponent()
{
addAndMakeVisible(spin1);
addAndMakeVisible(spin2);
addAndMakeVisible(spin3);
addAndMakeVisible(spin4);
spin1.addListener(this);
spin2.addListener(this);
spin3.addListener(this);
spin4.addListener(this);
addAndMakeVisible(label_1);
addAndMakeVisible(label_2);
addAndMakeVisible(label_3);
addAndMakeVisible(label_4);
label_1.setText(juce::CharPointer_UTF8("阈值(dBu)"), juce::dontSendNotification);
label_2.setText(juce::CharPointer_UTF8(("比率")), juce::dontSendNotification);
label_3.setText(juce::CharPointer_UTF8("启动时间(ms)"), juce::dontSendNotification);
label_4.setText(juce::CharPointer_UTF8("恢复时间(ms)"), juce::dontSendNotification);
}
ExtenderComponent::~ExtenderComponent()
{
}
void ExtenderComponent::resized()
{
label_1.setBounds(40, 10, 60, 28);
label_2.setBounds(110, 10, 60, 28);
label_3.setBounds(180, 10, 60, 28);
label_4.setBounds(250, 10, 60, 28);
spin1.setBounds(40, 50, 60, 25);
spin2.setBounds(110, 50, 60, 25);
spin3.setBounds(180, 50, 60, 25);
spin4.setBounds(250, 50, 60, 25);
}
void ExtenderComponent::paint(juce::Graphics& g)
{
// 填充背景色
g.fillAll(juce::Colours::darkgrey);
// 设置边框属性
const float cornerSize = 0.0f; // 圆角半径
const float borderThickness = 0.5f; // 边框粗细
// 获取组件边界(向内缩进1像素防止边缘裁剪)
auto bounds = getLocalBounds().toFloat().reduced(1.0f);
// 绘制白色圆角边框
g.setColour(juce::Colours::white);
g.drawRoundedRectangle(bounds, cornerSize, borderThickness);
}
void ExtenderComponent::ValueChanged(SpinBox* spinBox)
{
if (spinBox == &spin1)
{
// 处理 spin1 值变化
DBG("SpinBox 1 value: " << spin1.getValue());
}
else if (spinBox == &spin2)
{
// 处理 spin2 值变化
DBG("SpinBox 2 value: " << spin2.getValue());
}
else if (spinBox == &spin3)
{
// 处理 spin3 值变化
DBG("SpinBox 3 value: " << spin3.getValue());
}
else if (spinBox == &spin4)
{
// 处理 spin4 值变化
DBG("SpinBox 4 value: " << spin4.getValue());
}
}
运行效果:


浙公网安备 33010602011771号