添加版本控制 version=255为完整版本

修改起飞方案模拟实际航点的起飞模式
添加校准指南针和加速计
This commit is contained in:
zxd 2019-01-02 11:35:32 +08:00
parent 7f0fad0112
commit e2dcb04d62
17 changed files with 508 additions and 114 deletions

View File

@ -19,6 +19,7 @@ using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Plane.CommunicationManagement;
using Plane.FormationCreator.Util;
namespace Plane.FormationCreator
{
@ -68,6 +69,8 @@ namespace Plane.FormationCreator
};
//new Test().Prepare().Run();
VersionControl.GetVersionFromIni();
}
private ILogger _logger;

View File

@ -374,8 +374,8 @@ namespace Plane.FormationCreator.Formation
CurrentRunningTask = null;
//起飞任务需要跳过
if (CurrentRunningTaskIndex == 0)
CurrentRunningTaskIndex++;
// if (CurrentRunningTaskIndex == 0)
// CurrentRunningTaskIndex++;
CurrentRunningTaskIndex++;
await RunTaskAsync();
}
@ -572,6 +572,7 @@ namespace Plane.FormationCreator.Formation
{
var singleCopterInfoObj = singleCopterInfos[i];
takeOffTask.SingleCopterInfos[i].TakeOffWaitTime = (ushort)singleCopterInfoObj.waitTime;
//Message.Show(((ushort)singleCopterInfoObj.waitTime).ToString());
}
}

View File

@ -19,7 +19,7 @@ namespace Plane.FormationCreator.Formation
return info;
}
private ushort _LandWaitTime = 5;
private ushort _LandWaitTime = 1;
public ushort LandWaitTime
{
get { return _LandWaitTime; }

View File

@ -76,7 +76,15 @@ namespace Plane.FormationCreator.Formation
var tasksTakeOff = new Task[infos.Count];
for (int i = 0; i < infos.Count; i++)
{
tasksTakeOff[i] = NewSingleRunTaskOffTaskAsunc(infos[i]);
//tasksTakeOff[i] = NewSingleRunTaskOffTaskAsunc(infos[i]);
tasksTakeOff[i] = await Task.Factory.StartNew(async () =>
{
var internalInfo = infos[i];
await NewSingleRunTaskOffTaskAsunc(internalInfo);
});
}
await Task.WhenAll(tasksTakeOff).ConfigureAwait(false);
//await Task.Delay(100);
@ -91,30 +99,23 @@ namespace Plane.FormationCreator.Formation
int copterIndex = SingleCopterInfos.IndexOf(info);
var copter = info.Copter;
await copter.UnlockAsync();
for (int i = 0; !copter.IsUnlocked; i++)
{
//8秒内每1000毫秒尝试解锁一次
//解锁间隔一定要超过1s否则导致飞控以后无法解锁
if (i > 320)
return; //无法解锁后面不用执行了
if (i % (1000 / 25) == 1000 / 25 - 1)
{
await copter.UnlockAsync(); // 每 1000 毫秒重试一次。
}
await Task.Delay(25).ConfigureAwait(false);
}
//等待起飞时间
while (ts.TotalMilliseconds < info.TakeOffWaitTime * 1000)
Windows.Messages.Message.Show($"{copter.Name}:等待起飞 = {info.TakeOffWaitTime}");
while ((int)ts.TotalMilliseconds < (int)info.TakeOffWaitTime * 1000)
{
if (_flightTaskManager.IsPaused == true)
{
await info.Copter.HoverAsync();
return;
}
await Task.Delay(100);
dtNow = DateTime.Now;
ts = dtNow - dtLastTime;
}
//虚拟飞机5秒后不起飞会自动上锁
await copter.UnlockAsync();
for (int i = 0; i < 5; i++) // added by ZJF
{
await copter.TakeOffAsync();
@ -124,6 +125,9 @@ namespace Plane.FormationCreator.Formation
var copterNextTask = _flightTaskManager.Tasks[TaskIndex + 1].SingleCopterInfos[copterIndex];
float takeOffAlt = copterNextTask.TargetAlt;
info.TargetLat = info.Copter.Latitude;
info.TargetLng = info.Copter.Longitude;
for (int j = 0; j < 3; j++)
{
await info.Copter.FlyToAsync(info.TargetLat, info.TargetLng, takeOffAlt);
@ -133,8 +137,16 @@ namespace Plane.FormationCreator.Formation
dtNow = DateTime.Now;
ts = dtNow - dtLastTime;
FlightTask task = _flightTaskManager.CurrentRunningTask;
Windows.Messages.Message.Show($"{copter.Name}:等待到达飞行时间 = {task.TakeOffTime}");
while (ts.TotalMilliseconds < task.TakeOffTime * 1000)
{
if (_flightTaskManager.IsPaused == true)
{
//Windows.Messages.Message.Show($"{copter.Name}:悬停");
await info.Copter.HoverAsync();
return;
}
await Task.Delay(100).ConfigureAwait(false);
dtNow = DateTime.Now;
ts = dtNow - dtLastTime;

View File

@ -0,0 +1,19 @@
using Plane.FormationCreator.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Plane.FormationCreator.Formation
{
public class View3DManager
{
public View3DViewModel _view3DViewModel { get; set; }
public void ClearCopters()
{
this._view3DViewModel.Clear3DCopters();
}
}
}

View File

@ -12,12 +12,7 @@
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<WrapPanel>
<StackPanel Orientation="Horizontal" Margin="2" >
<Button Content="检测通信" Tag="FS_GCS_ENABLE" Click="Modify_Select" Width="130"/>
<Label Content="_FS_GCS_ENABLE 0:关闭 1开启"/>
</StackPanel>
<WrapPanel>
<StackPanel Orientation="Horizontal" Margin="2">
<Button Content="返航高度" Tag="RTL_ALT" Click="Modify_Select" Width="130"/>
<Label Content="_RTL_ALT 单位cm"/>
@ -28,6 +23,24 @@
<Label Content="_NTF_LED_BRIGHT 1-3"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="2" >
<Button Content="š返航关灯" Tag="NTF_G_RTLOFF" Click="Modify_Select" Width="130"/>
<Label Content="_NTF_G_RTLOFF"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="2">
<Button Content="š开关灯" Tag="NTF_G_OFF" Click="Modify_Select" Width="130"/>
<Label Content="_NTF_G_OFF"/>
</StackPanel>
</WrapPanel>
<StackPanel Name="hide_panel">
<Separator Foreground="#FFC1D1C6" />
<StackPanel Orientation="Horizontal" Margin="2" >
<Button Content="检测通信" Tag="FS_GCS_ENABLE" Click="Modify_Select" Width="130"/>
<Label Content="_FS_GCS_ENABLE 0:关闭 1开启"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="2" >
<Button Content="GPS类型灯光" Tag="NTF_G_RTKTEST" Click="Modify_Select" Width="130"/>
<Label Content="_NTF_G_RTKTEST 0:关闭 1:开启"/>
@ -43,11 +56,6 @@
<Label Content="_ARMING_VOLT_MIN"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="2" >
<Button Content="š返航灯光" Tag="NTF_G_RTLOFF" Click="Modify_Select" Width="130"/>
<Label Content="_NTF_G_RTLOFF"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="2" >
<Button Content="š航点灯光" Tag="WAYPOINT_GLED" Click="Modify_Select" Width="130"/>
<Label Content="_WAYPOINT_GLED"/>
@ -67,20 +75,17 @@
<Button Content="š返航GPS类型" Tag="FS_GPS_RTL" Click="Modify_Select" Width="130"/>
<Label Content="_FS_GPS_RTL 1-6"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="2">
<Button Content="š开关灯" Tag="NTF_G_OFF" Click="Modify_Select" Width="130"/>
<Label Content="_NTF_G_OFF"/>
</StackPanel>
</WrapPanel>
<StackPanel Grid.Column="1" HorizontalAlignment="Center" Orientation ="Horizontal">
<WrapPanel VerticalAlignment="Center" Orientation="Vertical" >
<Label x:Name="label" Content="参数名称" Margin="5,0,5,5"/>
<TextBox x:Name="textParamName" Width="160" Margin="5" IsReadOnly="False"/>
<TextBox x:Name="textParamName" Width="160" Margin="5" />
<Label x:Name="label_Copy" Content="参数值" Margin="5"/>
<TextBox x:Name="textParamValue" Margin="5"/>
<Button x:Name="btnModify" Content="修改" Width="100" Margin="10" Click="btnModify_Click"/>
<Button x:Name="btnLoad" Content="读取" Width="100" Margin="10" Click="btnLoad_Click"/>
</WrapPanel>
</StackPanel>

View File

@ -1,4 +1,5 @@
using System;
using Plane.FormationCreator.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -19,20 +20,33 @@ namespace Plane.FormationCreator
/// </summary>
public partial class ModifyParam : Window
{
public bool LoadParam = false;
public ModifyParam()
{
InitializeComponent();
if (!VersionControl.IsFullVersion)
{
hide_panel.Visibility = Visibility.Collapsed;
textParamName.IsReadOnly = true;
btnLoad.Visibility = Visibility.Collapsed;
}
}
private void btnModify_Click(object sender, RoutedEventArgs e)
{
LoadParam = false;
this.DialogResult = true;
}
private void Modify_Select(object sender, RoutedEventArgs e)
{
textParamName.Text = ((Button)sender).Tag.ToString();
}
private void btnLoad_Click(object sender, RoutedEventArgs e)
{
LoadParam = true;
this.DialogResult = true;
}
}
}

View File

@ -191,6 +191,7 @@
<Compile Include="Test.cs" />
<Compile Include="CalculationLogLatDistance.cs" />
<Compile Include="Util\ParamFile.cs" />
<Compile Include="Util\VersionControl.cs" />
<Compile Include="ViewModels\CalibrationViewModel.cs" />
<Compile Include="ViewModels\ConnectViewModel.cs" />
<Compile Include="ViewModels\ControlPanelViewModel.cs" />

View File

@ -0,0 +1,25 @@
using Plane.Windows.IniHelper;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Plane.FormationCreator.Util
{
public static class VersionControl
{
public static int Version = 0;
public static bool IsFullVersion = false;
public static void GetVersionFromIni()
{
IniFiles iniFiles = new IniFiles();
string readvalue = iniFiles.IniReadvalue("Version", "Version");
int intTemp;
if (readvalue != "" && int.TryParse(readvalue, out intTemp))
Version = int.Parse(readvalue);
IsFullVersion = Version == 255;
}
}
}

View File

@ -0,0 +1,224 @@
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using Microsoft.Practices.ServiceLocation;
using Plane.CommunicationManagement;
using Plane.CopterManagement;
using Plane.Copters;
using Plane.FormationCreator.Formation;
using Plane.Windows.Messages;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace Plane.FormationCreator.ViewModels
{
public class CalibrationViewModel : ViewModelBase
{
CommModuleManager commModule = CommModuleManager.Instance;
private CopterManager _copterManager = ServiceLocator.Current.GetInstance<CopterManager>();
private string _AccelerometerTips;
public string AccelerometerTips
{
get
{
switch (AccelerometerState)
{
case AccelerometerStates.Idle:
_AccelerometerTips = "点击“开始校准”后,开始校准加速计";
break;
case AccelerometerStates.Front:
_AccelerometerTips = "亮紫色: 飞机水平放置(Front)";
break;
case AccelerometerStates.Left:
_AccelerometerTips = "亮黄色:飞机左侧接触地面竖立放置(Left)";
break;
case AccelerometerStates.Right:
_AccelerometerTips = "亮青色:飞机右侧接触地面竖立放置(Right)";
break;
case AccelerometerStates.Down:
_AccelerometerTips = "紫色:飞机机头向下接触地面竖立放置(Down)";
break;
case AccelerometerStates.Up:
_AccelerometerTips = "黄色:飞机机尾向下接触地面竖立放置(Up)";
break;
case AccelerometerStates.Back:
_AccelerometerTips = "青色:飞机翻过来水平放置(Back)";
break;
}
return _AccelerometerTips;
}
}
private string _AccelerometerBtnText = "开始校准";
public string AccelerometerBtnText
{
get
{
switch (AccelerometerState)
{
case AccelerometerStates.Idle:
_AccelerometerBtnText = "开始校准";
break;
default:
_AccelerometerBtnText = "完成";
break;
}
return _AccelerometerBtnText;
}
}
//校准加速计的状态
public enum AccelerometerStates
{
Idle = 0,
Front = 1,
Left = 2,
Right = 3,
Down = 4,
Up = 5,
Back = 6
}
private AccelerometerStates _AccelerometerState = AccelerometerStates.Idle;
public AccelerometerStates AccelerometerState
{
get { return _AccelerometerState; }
set
{
Set(nameof(AccelerometerState), ref _AccelerometerState, value);
RaisePropertyChanged(nameof(AccelerometerBtnText));
RaisePropertyChanged(nameof(AccelerometerTips));
}
}
private ICommand _CalibrationAccelerometerCommand;
public ICommand CalibrationAccelerometerCommand
{
get
{
return _CalibrationAccelerometerCommand ?? (_CalibrationAccelerometerCommand = new RelayCommand(async () =>
{
ICopter copter = _copterManager.SelectedCopters.FirstOrDefault();
short copterId = short.Parse(copter.Name);
switch (AccelerometerState)
{
case AccelerometerStates.Idle:
await commModule.DoStartPreflightCompassAsync(copterId);
break;
default:
await commModule.DoNextPreflightCompassAsync(copterId);
break;
}
if (AccelerometerState == AccelerometerStates.Back)
{
Alert.Show("校准结束,请检测灯光后重启飞机!\r\n 绿色:校准成功 红色:校准失败");
AccelerometerState = AccelerometerStates.Idle;
}
else
{
AccelerometerState = (AccelerometerStates)((int)AccelerometerState + 1);
}
}));
}
}
private int _CompassPercent;
public int CompassPercent
{
get { return _CompassPercent; }
set
{
Set(nameof(CompassPercent), ref _CompassPercent, value);
}
}
public bool IsCalibration { get; set; }
/// <summary>
/// 校准指南针
/// </summary>
private ICommand _CalibrationCompassCommand;
public ICommand CalibrationCompassCommand
{
get
{
return _CalibrationCompassCommand ?? (_CalibrationCompassCommand = new RelayCommand(async () =>
{
if (IsCalibration || _copterManager.SelectedCopters.Count() != 1) return;
ICopter copter = _copterManager.SelectedCopters.FirstOrDefault();
short copterId = short.Parse(copter.Name);
Message.Show("开始校准指南针:" + copter.Name);
//for (int i = 0; i < 3; i++)
//{
await commModule.DoCalibrationCompassAsync(copterId);
await Task.Delay(50).ConfigureAwait(false);
//}
IsCalibration = true;
CompassPercent = 0;
int State = 0; //4成功 5失败 todo 改为枚举
while (IsCalibration && CompassPercent <= 100)
{
//两个255的时候表示 当前预留字节代表的意思是校准
if (copter.Retain[2] == 255 && copter.Retain[3] == 255)
{
CompassPercent = copter.Retain[0];
State = copter.Retain[1];
if (State == 4 || State == 5)
break;
}
await Task.Delay(100);
}
switch (State)
{
case 4:
Alert.Show($"校准成功,请重新上电{copterId}号", "指南针");
break;
case 5:
Alert.Show("校准失败", "指南针");
break;
default:
break;
}
IsCalibration = false;
}));
}
}
private ICommand _CancelCalibrationCompassCommand;
public ICommand CancelCalibrationCompassCommand
{
get
{
return _CancelCalibrationCompassCommand ?? (_CancelCalibrationCompassCommand = new RelayCommand(async () =>
{
if (_copterManager.SelectedCopters.Count() != 1) return;
ICopter copter = _copterManager.SelectedCopters.FirstOrDefault();
short copterId = short.Parse(copter.Name);
await commModule.DoCancelCalibrationCompassAsync(copterId);
IsCalibration = false;
Alert.Show("放弃校准", "指南针");
}));
}
}
}
}

View File

@ -596,8 +596,11 @@ namespace Plane.FormationCreator.ViewModels
var ModifyParamWindow = new Plane.FormationCreator.ModifyParam();
if (ModifyParamWindow.ShowDialog() == true)
{
bool isLoadParam = ModifyParamWindow.LoadParam;
string paramstr = ModifyParamWindow.textParamName.Text;
if(!isLoadParam)
{
float paramvalue = Convert.ToSingle(ModifyParamWindow.textParamValue.Text);
int num = 0;
if (_copterManager.AcceptingControlCopters.Count() < _copterManager.Copters.Count)
@ -605,6 +608,15 @@ namespace Plane.FormationCreator.ViewModels
else if (_copterManager.AcceptingControlCopters.Count() == _copterManager.Copters.Count)
num = await _commModuleManager.SetParamAsync(paramstr, paramvalue);
Alert.Show($"广播完成! 当前序列号:{num}");
}
else
{
if (_copterManager.AcceptingControlCopters.Count() < _copterManager.Copters.Count)
await _commModuleManager.ReadParamAsnyc(paramstr, _copterManager.AcceptingControlCopters);
else if (_copterManager.AcceptingControlCopters.Count() == _copterManager.Copters.Count)
await _commModuleManager.ReadParamAsnyc(paramstr);
}
/*
await Task.WhenAll(_copterManager.AcceptingControlCopters.Select
(copter => copter.SetParamAsync(paramstr, paramvalue)));

View File

@ -381,7 +381,7 @@ namespace Plane.FormationCreator.ViewModels
double minlat = 0;
double avgl = 0;
/*
if (_copterManager.SelectedCopters.Count() > 2)
{
List<FlightTaskSingleCopterInfo> selectTaskInfos = new List<FlightTaskSingleCopterInfo>();
@ -401,8 +401,8 @@ namespace Plane.FormationCreator.ViewModels
selectTaskInfos[i].TargetLat = minlat + i * tlat;
}
}
*/
/*
if (Alert.Show("本操作将导致飞机位置重新排列,编号最小的飞机在最上边,您确定要继续吗?", "提示", MessageBoxButton.OKCancel, MessageBoxImage.Warning)
== MessageBoxResult.OK)
{
@ -464,6 +464,7 @@ namespace Plane.FormationCreator.ViewModels
// await Task.Delay(100); // 如果不等待一段时间,很可能会再触发 DataStreamReceived 事件导致飞行器重新出现在地图上。
}
}
*/
}));
}
}

View File

@ -0,0 +1,36 @@
<Window x:Class="Plane.FormationCreator.Views.CalibrationWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Plane.FormationCreator.Views"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Title="校准" Height="320" Width="450">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Margin="10" >
<Label Content="加速计" Margin="0,0,0,10" FontWeight="Bold" FontSize="14"/>
<Button Content="{Binding AccelerometerBtnText,UpdateSourceTrigger=Default}" Width="100"
Command="{Binding CalibrationAccelerometerCommand}"/>
<Label Margin="0,10" Content="{Binding AccelerometerTips, UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="Center"/>
</StackPanel>
<StackPanel Grid.Row="1" Margin="10,0,10,10">
<Label Content="指南针" Margin="0,0,0,10" FontWeight="Bold" FontSize="14"/>
<WrapPanel HorizontalAlignment="Center">
<Button Content="开始校准" Width="100" Margin="10, 0"
Command="{Binding CalibrationCompassCommand}"/>
<Button Content="放弃校准" Width="100" Margin="10, 0"
Command="{Binding CancelCalibrationCompassCommand}"/>
</WrapPanel>
<ProgressBar Margin="0,20" Height="18" Width="220" Value="{Binding CompassPercent, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</Grid>
</Window>

View File

@ -0,0 +1,36 @@
using Microsoft.Practices.ServiceLocation;
using Plane.FormationCreator.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Plane.FormationCreator.Views
{
/// <summary>
/// CalibrationWindow.xaml 的交互逻辑
/// </summary>
public partial class CalibrationWindow : Window
{
public CalibrationWindow()
{
InitializeComponent();
CalibrationViewModel calibrationViewModel = ServiceLocator.Current.GetInstance<CalibrationViewModel>();
calibrationViewModel.AccelerometerState = CalibrationViewModel.AccelerometerStates.Idle;
this.DataContext = calibrationViewModel;
}
}
}

View File

@ -8,8 +8,8 @@
xmlns:ec="clr-namespace:Plane.Windows.Controls;assembly=Plane.Windows"
mc:Ignorable="d"
Title="连接"
Width="429.175"
Height="570.371"
Width="432.175"
Height="300.371"
WindowStartupLocation="CenterScreen"
FontFamily="Microsoft YaHei"

View File

@ -94,6 +94,7 @@
</StackPanel>
<StackPanel>
<TextBlock Text="返回数据:" />
<StackPanel ToolTip="{Binding RetainInt}">
<TextBlock Text="{Binding Retain[3]}" />
<TextBlock Text="." />
<TextBlock Text="{Binding Retain[2]}" />
@ -101,6 +102,8 @@
<TextBlock Text="{Binding Retain[1]}" />
<TextBlock Text="." />
<TextBlock Text="{Binding Retain[0]}" />
</StackPanel>
</StackPanel>

View File

@ -286,6 +286,7 @@
</TabItem>
<TabItem Header="灯光设计">
<StackPanel>
<StackPanel >
<StackPanel Orientation="Horizontal" Margin="0,5,0,5" >
<TextBlock Margin="5,7,0,0" Text="起始时间"/>
@ -361,8 +362,9 @@
<ItemsControl Name="LEDItems" Margin="5"
Grid.Row="1"
Grid.ColumnSpan="2"
MinHeight="180"
MaxHeight="350"
MinHeight="100"
Height="Auto"
MaxHeight="280"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ItemsSource="{Binding Path= LEDInfos}">
@ -400,16 +402,16 @@
</ComboBox>
<TextBlock Text="时间" Margin="12,0,0,0" VerticalAlignment="Center"></TextBlock>
<TextBox MinWidth="40" MaxWidth="50" Margin="5,0,0,0"
<TextBox MinWidth="40" MaxWidth="45" Margin="5,0,0,0"
ToolTip="单位:秒最小设置0.1秒"
Text="{Binding Delay,UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="频率" Margin="12,0,0,0" VerticalAlignment="Center"></TextBlock>
<TextBox MinWidth="40" MaxWidth="50" Margin="5,0,0,0"
<TextBox MinWidth="40" MaxWidth="45" Margin="5,0,0,0"
Text="{Binding Path=LEDRate, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="颜色" Margin="12,0,0,0" VerticalAlignment="Center"></TextBlock>
<TextBox Margin="5,0,0,0" MinWidth="60" Width="60"
<TextBox Margin="5,0,0,0" MinWidth="50" Width="50"
Text="{Binding LEDRGB, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="删除" Margin="12,0,0,0"