张高兴的 UWP 开发笔记:用 Thumb 控件仿制一个可拖动 Button

本文介绍如何在UWP中使用Thumb控件制作一个可拖动的圆形按钮,并实现按钮点击打开侧边菜单的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  在 WPF 上可用的控件拖动方法在 UWP 上大多没用,那干脆用 Thumb 仿制一个吧。

  关于 Thumb 控件的教程也不多,毕竟在 WPF 控件拖动有很多种方法, Thumb 就显得很鸡肋了。下面我就简单的说说。(MSDN 文档)

  不谈什么属性和方法,大多数都是继承的。主要说说 Thumb 的原生事件 DragStartedDragDeltaDragCompleted

  DragStarted 和字面意思差不多,开始拖动的时候发生的。

  DragDelta 拖动进行中,只要你鼠标不放就会一直进行。

  DragCompleted 拖动结束后发生。

 

  下面就来仿制一个可以拖动的圆形 Button,像 IPhone 的“小圆点”一样(像下图一样),只不过功能单一,仅仅用来打开 MainPage 里的汉堡菜单。本文仿制 Button 的时候只需要 DragDelta 事件。

  实现很简单,我就不写示例了。

  我有一个 SplitView “RootSplitView”,作为汉堡菜单的容器。

  首先需要在合适的页面敲上一个 <Thumb />,给它个 Name="RootThumb",我是把它放在页面右下角的。

<Thumb Name="RootThumb" Height="55" Width="55" HorizontalAlignment="Right" VerticalAlignment="Bottom" Canvas.ZIndex="101" />

  这时设计器右下角应该出现了一个方块,但它不是我需要的圆形,下面打开 Blend 进行样式定制(前面讲 横向ListView 时提过)。默认的 Thumb 样式如下。

<Style x:Key="ThumbStyle1" TargetType="Thumb">
            <Setter Property="Background" Value="{ThemeResource ThumbBackground}"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="IsTabStop" Value="False"/>
            <Setter Property="BorderBrush" Value="{ThemeResource ThumbBorderBrush}"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Thumb">
                        <Grid>
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal"/>
                                    <VisualState x:Name="PointerOver">
                                        <Storyboard>
                                            <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundPointerOver"/>
                                            <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Background"/>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Pressed">
                                        <Storyboard>
                                            <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundPressed"/>
                                            <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Background"/>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Disabled"/>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <Border x:Name="Background" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"/>
                            <Border x:Name="BackgroundPointerOver" BorderBrush="{ThemeResource ThumbBorderBrushPointerOver}" BorderThickness="{TemplateBinding BorderThickness}" Background="{ThemeResource ThumbBackgroundPointerOver}" Opacity="0"/>
                            <Border x:Name="BackgroundPressed" BorderBrush="{ThemeResource ThumbBorderBrushPressed}" BorderThickness="{TemplateBinding BorderThickness}" Background="{ThemeResource ThumbBackgroundPressed}" Opacity="0"/>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

  因为需要一个圆形并且里面有个汉堡菜单的图标的仿制 Button,我们需要在默认样式提过的 RootGrid 里画个圆,顺便来个 TextBlock 用来显示汉堡菜单的图标。定制好的样式如下。

<Style x:Key="ThumbStyle1" TargetType="Thumb">
            <Setter Property="Background" Value="Transparent"/>
            <Setter Property="BorderThickness" Value="1"/>
            <Setter Property="IsTabStop" Value="False"/>
            <Setter Property="BorderBrush" Value="{ThemeResource ThumbBorderBrush}"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Thumb">
                        <Grid x:Name="RootGrid">
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal"/>
                                    <VisualState x:Name="PointerOver">
                                        <Storyboard>
                                            <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundPointerOver"/>
                                            <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Background"/>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Pressed">
                                        <Storyboard>
                                            <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundPressed"/>
                                            <DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Background"/>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Disabled"/>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <Rectangle RadiusY="25" RadiusX="25" Fill="Gray" Opacity="0.6" Stroke="{ThemeResource SystemControlBackgroundAccentBrush}" StrokeThickness="3" />
                            <TextBlock FontFamily="Segoe MDL2 Assets" Text="&#xE700;" FontSize="22" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center" />
                            <Border x:Name="Background" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"/>
                            <Border x:Name="BackgroundPointerOver" BorderBrush="{ThemeResource ThumbBorderBrushPointerOver}" BorderThickness="{TemplateBinding BorderThickness}" Background="Transparent" Opacity="0"/>
                            <Border x:Name="BackgroundPressed" BorderBrush="{ThemeResource ThumbBorderBrushPressed}" BorderThickness="{TemplateBinding BorderThickness}" Background="Transparent" Opacity="0"/>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

  这时,设计器里应该出现了这个玩意儿(得意~~~)

  Button 的 Click 事件怎么实现呢?有 PointerPressed 和 Tapped 两个备选事件。现在的交互事件有三种:Mouse Events(鼠标事件),Touch Events(触摸事件)和 Pointer Events(指针事件),分别为不同的设备提供不同的交互方式。说这么多废话其实直接试试就好了。。。在 Thumb 的 xml 标记里添加 Tapped="RootThumb_Tapped",事件代码如下

private void MainThumb_Tapped(object sender, TappedRoutedEventArgs e)
{
  RootSplitView.IsPaneOpen = !RootSplitView.IsPaneOpen;
}

  下面说说拖动怎么实现,需要编写 DragDelta 事件。由于 Win10 设备体系庞大,UWP 上谈控件坐标没啥意义,这也正是 WPF 上的控件拖动方案没用的原因。如果你在设计器里像 WinForm 一样拖拽控件设计布局的话,xaml 会给被拖拽的控件一个 Margin,因此 Thumb 的拖拽实现也用的 Margin。首先你需要定义两个 double 私有字段记录 X, Y 轴的位移量。设计目的是 Thumb 在右下角,而页面的坐标零点在左上角,只需要将 Thumb 的 Margin 的 Right,Bottom 给一个位移量的负值即可。完整代码如下。

private void MainThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
  // 两个 double 类型,用来记录偏移量   thumbX
+= e.HorizontalChange;   thumbY += e.VerticalChange;   MainThumb.Margin = new Thickness(0, 0, -thumbX, -thumbY); }

  

  这样,一个圆形的可拖动 Button 就用 Thumb 仿制完成了。

<think>我们正在使用虾哥开源的小智项目(xiaozhi-esp32)通过MCP协议控制LED灯。MCP(Micro-Controller Protocol)是该项目中用于设备间通信的协议。以下是具体步骤:###一、硬件连接-LED正极 →ESP32的GPIO引脚(例如GPIO2,串联限流电阻) -LED负极 →GND###二、代码修改####1.注册LED设备在`device_manager.cpp`中注册LED设备: ```cpp#include "devices/led_device.h"//包含LED设备头文件void DeviceManager::InitializeDevices(){// ...其他设备初始化代码AddDevice(std::make_shared<LedDevice>());//注册LED设备}``` ####2.创建LED设备类- **头文件**`led_device.h`:```cpp#pragmaonce#include "thing.h"class LedDevice :public Thing{ public:LedDevice();void TurnOn();voidTurnOff();//通过MCP协议控制需要实现HandleMethod方法boolHandleMethod(const std::string& method, constJsonObject& params, JsonObject&result)override;private:boolstatus_;constgpio_num_tgpio_ =GPIO_NUM_2;//使用GPIO2}; ```- **实现文件**`led_device.cpp`:```cpp#include"led_device.h" #include<driver/gpio.h> LedDevice::LedDevice() :Thing("LED","MCP控制的LED灯") {gpio_reset_pin(gpio_);gpio_set_direction(gpio_,GPIO_MODE_OUTPUT);gpio_set_level(gpio_,0);//初始关闭status_ =false;//注册属性(用于MCP协议状态报告)properties_.AddBooleanProperty("status","当前状态",[this](){return status_; });//注册方法(MCP调用)methods_.AddMethod("TurnOn","开灯",ParameterList(), [this](constParameterList&){ TurnOn();});methods_.AddMethod("TurnOff","关灯",ParameterList(), [this](const ParameterList&) {TurnOff(); });} voidLedDevice::TurnOn(){status_= true;gpio_set_level(gpio_,1);//高电平点亮} voidLedDevice::TurnOff(){status_= false;gpio_set_level(gpio_,0);//低电平熄灭}//处理MCP调用boolLedDevice::HandleMethod(conststd::string&method,const JsonObject&params,JsonObject& result) {if(method== "TurnOn") {TurnOn();result["status"] ="success";return true;} elseif (method =="TurnOff"){TurnOff();result["status"]= "success";returntrue;}returnfalse;//方法未处理}``` ###三、通过MCP协议控制LED小智项目内置了MCP服务器,其他设备(如手机APP、网页)可通过MCP协议发送控制命令。协议格式示例: ```json{"device": "LED",//设备名称"method": "TurnOn",//方法名"params":{}//参数(本例无参数) }```###四、测试MCP控制1. **启动MCP服务器**:小智项目启动时会自动监听MCP端口(默认`6789`)。 2. **发送控制命令**(使用`netcat`测试):```bashecho '{"device":"LED","method":"TurnOn","params":{}}' |nc192.168.1.1006789```响应示例:```json{"status":"success"}``` 3. **观察LED状态**:发送命令后LED应点亮。###五、结合BotSharp智能体框架(可选) BotSharp可作为上层应用,通过MCP协议控制小智设备[^2]。在BotSharp中创建智能体时,调用MCP接口:```csharp// BotSharp智能体示例代码publicasyncTask<string> ControlLed(string deviceIp,stringaction) {using(varclient =new HttpClient()){var mcpCommand= new{device= "LED",method= action== "on"? "TurnOn" :"TurnOff",params= new{ }};varresponse= awaitclient.PostAsJsonAsync($"http://{deviceIp}:6789", mcpCommand);return awaitresponse.Content.ReadAsStringAsync();} }```>提示:实际开发中需处理网络异常和超时。§§相关问题§§1.MCP协议如何实现设备发现? 2.如何在小智项目中加密MCP通信? 3.如何通过MQTT转发MCP命令实现远程控制? 4. BotSharp智能体如何解析自然语言生成MCP命令? [^1]:创客社区地址BotSharp文档桌面桌面机器人仓库地址.NETIoT库源码地址.NET IoT官方地址.NET IoT社区人员高兴推荐什么是lottie动画?参考的语音对话项目conversational-speaker小智C#客户端C#MCPSDK树莓派学习之旅(2)—使用USB声卡进行录音BotSharp官方GitHubEdgeVoiceAI聊天机器人[^2]:结合BotSharp智能体框架开发语音交互。BotSharp是一个开源的多智能体应用开发框架,从简单的聊天机器人,再到多智能体协作,以及复杂的任务如【TextTo Sql】框架都提供了开箱即用的使用方法,可以快速的将大模型的能力接入到现有的业务系统中,并且内置知识库和会话管理功能等。大语言模型的函数调用(这个是理解BotSharp框架的核心知识点)。</think>### 如何使用虾哥开源小智项目通过MCP协议控制LED灯 以下是基于小智项目通过MCP协议实现LED控制的完整步骤,结合硬件连接、代码修改和协议配置: #### 一、硬件准备 1. **ESP32开发板**:使用支持MCP协议的ESP32开发板 2. **LED连接**: - LED正极 → GPIO2(串联220Ω限流电阻) - LED负极 → GND 3. **网络连接**:确保ESP32接入局域网 #### 二、代码修改(小智项目端) ##### 1. 创建LED设备类 在`devices`目录下创建`led_device.h`: ```cpp #pragma once #include "thing.h" #include <driver/gpio.h> class LedDevice : public Thing { public: LedDevice(); void TurnOn(); void TurnOff(); bool HandleMCPCommand(const std::string& method, const JsonObject& params); // MCP协议处理 private: bool status_ = false; const gpio_num_t gpio_ = GPIO_NUM_2; // 使用GPIO2 }; ``` ##### 2. 实现LED控制逻辑(`led_device.cpp`) ```cpp #include "led_device.h" LedDevice::LedDevice() : Thing("LED", "MCP控制的LED灯") { gpio_reset_pin(gpio_); gpio_set_direction(gpio_, GPIO_MODE_OUTPUT); gpio_set_level(gpio_, 0); // 初始关闭 // 注册MCP可调用方法 methods_.AddMethod("TurnOn", "开灯", ParameterList()); methods_.AddMethod("TurnOff", "关灯", ParameterList()); } void LedDevice::TurnOn() { status_ = true; gpio_set_level(gpio_, 1); // 高电平点亮 } void LedDevice::TurnOff() { status_ = false; gpio_set_level(gpio_, 0); // 低电平熄灭 } // 处理MCP协议命令 bool LedDevice::HandleMCPCommand(const std::string& method, const JsonObject& params) { if (method == "TurnOn") { TurnOn(); return true; } else if (method == "TurnOff") { TurnOff(); return true; } return false; } ``` ##### 3. 注册设备并启用MCP服务 在`main.cpp`中: ```cpp #include "devices/led_device.h" #include "network/mcp_server.h" // MCP协议服务 void setup() { // ...其他初始化 DeviceManager::AddDevice(std::make_shared<LedDevice>()); // 启动MCP服务器(默认端口6789) MCPServer::Start(6789); } ``` #### 三、MCP协议控制端实现(C#示例) 使用C# MCP SDK发送控制命令[^1]: ```csharp using MCP.Client; class Program { static void Main() { // 连接到小智设备 var device = new McpDevice("192.168.1.100", 6789); // ESP32的IP // 发送开灯命令 var response = device.InvokeMethod("LED", "TurnOn"); if (response.Status == "success") { Console.WriteLine("开灯成功"); } else { Console.WriteLine($"控制失败: {response.Error}"); } } } ``` #### 四、MCP协议命令格式 通过TCP发送的原始MCP协议格式: ```json { "device": "LED", // 设备名称 "method": "TurnOn", // 调用的方法 "params": {}, // 参数(本例不需要) "request_id": 123 // 请求ID(可选) } ``` 成功响应示例: ```json { "status": "success", "device": "LED", "response": {} } ``` #### 五、测试流程 1. 烧录修改后的小智固件到ESP32 2. 启动C#控制程序(或使用网络调试工具发送MCP命令) 3. 观察LED状态变化 4. 调试命令: ```bash echo '{"device":"LED","method":"TurnOn"}' | nc 192.168.1.100 6789 ``` > 提示:BotSharp框架可作为上层应用生成MCP命令,实现自然语言控制LED[^2]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值