接了一个小杂毛项目,大概情形是这样的:ZWT先生开的店是卖拆片机的,Z先生不仅卖机器,还贴心地提供一项服务:可以根据顾客需要修改两个电机的转向和转速(机器厂家有给SDK的,但Z自己不会写程序)。厂家有配套一个调节器,调整参数时连接到拆片机的串口上,然后旋转按钮可以调速,拨码开关可以设定电机正转还是反转。但Z觉得调速器长得像个马桶水箱似的,既不好看也不便携带。最惨的是,有的客户的车间里有很多台机子,一台一台的连上串口调很费劲。于是,学过电工的Z不知道哪搞了一个串口 Hub,电脑向 Hub 发数据,然后会将数据群发给所有连接的拆片机(这种Hub忘了叫什么,十几年前老周曾在某工厂里见过)。

Z先生经过 N 个中间人介绍,找到老周。去Z的店参观了一下,了解情况后。当时老周可能被蚊子叮昏了头脑,觉得这不简单吗,便答应Z老板说三天可以完成。不过,这做起来确实不复杂,基础代码当天晚上用了两个多小时就搞定了。串口发数据这事也不难,就一个 SerialPort 类就搞定的。协议方面有文档,一个字节数组,代入参数就行。每台拆片机的编组号和参数就用一人 Sqlite 数据库存放就完事,配上 EF Core 读写起来那简直是入门级的难度。

不过,在第一次实测时遇到一个问题,每台机器的参数是不同的,需要提供一个界面,让用户可以增/删/改/查。也就是说,机器参数不能用类来封装,静态语言又不能动态生成属性。当然,这也不是静态语言的错,而是思路的问题。我干吗非要用类封装呢?不封装不就更可以动态变化了吗?然后,老周的生理 CPU 飞速运转,眼前浮现着一张张熟悉的面孔—— 用字典来存储,用JSON来做,用XML来做……避免产生过多不必要的数据文件,老周决定在数据库加个表,一个选项(参数)就是一条数据记录。字段如选项名称,选项值,备注,添加时间,最后修改时间,添加人(谁操作的)等。

随后,又有问题了,虽然在数据库中我把选项(参数)值统一用文本来表示,但在用户界面上,总不能全放个 TextBox 在那里。最重要的是全用 TextBox 的话,验证的代码就要写很多了。比如:你输入的是日期吗?你输入的是整数吗?Bool 值的东西为什么还要让人填呢,直接CheckBox不好吗?现在的情况是,机器参数是用户动态添加的,在布局界面时我不能写死了,毕竟事先不能确定用下拉列表还是文本框。那就在运行时动态呈现控件吧。

思路有了,但方法有很多种。老周当初吹下了三天完工的牛皮,所以,创建新控件的思路可能更花时间,而且感觉也没必要。经过短暂的计划,老周的想法:

1、在编辑界面上用一个 ListBox 或 ItemsControl,这样可以免去许多排版的功夫。就把界面当成一个列表框控件,每个被编辑的选项就是一个列表项。这样添加和删除也方便,直接 Binding 就好了。

2、为 ItemsControl 定义列表项模板,是 DataTemplate,不是控件模板。重做控件模板跟创作新控件一样,费时间。

3、DataTemplate 中,一个 TextBlock 用来显示项目标签(不可修改的文本),然后值呢?

4、选项值用的控件是不固定的,事先不可知,老周就放了个 ContentControl 在那里,就当作占位符用吧。为什么选它呢?因为它有 Content 属性,可以放任何东西,而且配套一个 ContentTemplate 属性,类型正是 DataTemplate。当然,也可以用类似功能的控件。

老周的想法就是,用 ContentTemplate 去引用其他的数据模板,当然数据模板也不能写死的。即要定义多个模板,里面放不同的控件,需要根据选项的需求选择不同的模板。于是,有一个类可以用上——DataTemplateSelector。

这个类有一个虚方法叫 SelectTemplate,能够根据相同数据运态返回数据模板。派生类只要重写这个方法就 OK。

哦,补充一下,什么是拆片机。大伙伴看看你身上穿的战袍,是不是由一块块布组成?服装厂先要依据图纸,用选定的线织成一块块的“散片”,针织机有横机和纵机,横向的多见。以前是手动操作,现在是电脑控制。这些“散片”可交给缝盘工人(做缝盘的一般是女孩,以前那些手动织机的话,男女都有)“组装”起来。下图是常见的缝盘机(不是缝纫机,二者不同的),老周家里还有三台,23年前的型号,有两台还是全新未拆封的。

缝盘机的套口编号要与针织机的匹配使用,如果混用会出问题的,以前内销毛衣常见的12针(这个简单说就是代表线眼的疏密)一般会配用14号的缝盘机(当然也不是绝对,看工人经验,有时候接近编号的也行)。

从上世纪 90 年代起,老周家里做过十几年的毛衣加工,现在是租给别人做,近20台电脑横机,全天24小时工作。所以,服装方面的东西,老周还是懂一点点的。

拆片就是把编织好的衣片拆解,变回一筒一筒的毛线。这个原因就很复杂,可能针织时效果不好,可能拿错图纸了,可能多出来不用的……把衣片重新变回毛线,还可以重复用,不浪费。有些片可能被机器里的润滑油污染,以前有的师傅懒得处理,是直接不要的(在农村,以前有些人家用来生火煮水煮饭),不过,这些毛料燃烧起来是环境污染,而且味道让人很难受。现在很少有人这么干。毕竟,别说燃气灶,连电磁炉在农村都很普及了。直接生火做饭的很少,就算要生火,也会选一些天然的柴木。

拆片机结构很简单,用夹子固定衣片,先人工抽出部分丝线,在橡皮轮上缠绕二三十圈,然后开动电机就行,工作起来有点像打毛机。拆片机现在并没有标准的产品,都是个别有“本事”的工厂或者像Z先生这样的,自己做的。

------------------------------------------------------------------------------------------------------------------------------------------------------------------

不得了,这次扯的废话太长了。不能再说了,不然就真变成水文了。下面说重点。

步骤一:定义选项包装类

哦,老周上面只是说,不用类来封装选项数据,但每条选项记录还是要封装的。这个类与数据库中的选项表映射。为了简单,老周删除了像备注、操作员、时间、父级选项等无关的属性。

public class OptionItem : INotifyPropertyChanged
{
private string _labelName;
private string _objValue;
private bool _required;
private UIElementType _uiType;
private string _listItems; public int Id { get; set; } /// <summary>
/// 选项的标签文本
/// </summary>
public string LabelName
{
get => _labelName;
set
{
_labelName = value;
OnMyPropertyChanged();
}
} /// <summary>
/// 选项的值
/// </summary>
public string ObjectValue
{
get { return _objValue; }
set
{
_objValue = value;
OnMyPropertyChanged();
}
} /// <summary>
/// 是否为必填
/// </summary>
public bool Required
{
get => _required;
set
{
_required = value;
OnMyPropertyChanged();
}
} /// <summary>
/// 界面控件类型
/// </summary>
public UIElementType UIType
{
get => _uiType;
set
{
_uiType = value;
OnMyPropertyChanged();
}
} /// <summary>
/// 显示在下拉列表中的内容,只有需要下拉列表控件时才用到
/// </summary>
public string ListItems
{
get => _listItems;
set
{
_listItems = value;
OnMyPropertyChanged();
}
} // 属性更改后调用的方法
private void OnMyPropertyChanged([CallerMemberName] string propertyName = null)
{
if (string.IsNullOrEmpty(propertyName))
{
return;
}
PropertyChanged?.Invoke(this, new(propertyName));
} // 属性更改通知事件
public event PropertyChangedEventHandler PropertyChanged;
}

实现 INotifyPropertyChanged 接口,这个不用多介绍,可以让绑定对象能实时获得更改通知。UIElementType 是一个枚举类型,表示要呈现什么样的控件,映射到数据库表时用整型存储。

public enum UIElementType
{
TextBox, // 普通文本框
NumberBoxInt, // 文本框,但输入整数用
NumberBoxFloat, // 文本框,但输入浮点数用
CheckBox, // 复选框
DropdownList, // 下拉列表框
DatePicker // 日期选择器
}

这老周项目所需要用的控件,你可以根据实际添加成员。WPF的编辑控件其实很少,比 WinForms 还少。大概开发团队觉得 WPF 的控件可以自己设计。虽然控件模板方便换,但做起来也要一定工作量的,反正嘛,非必要不创作控件。有大伙伴会问:怎么不用开源的第三方控件?如果老周自己用的话,倒是可以的。可是项目一旦是商用的就……有些开源协议是很让人无语的,而且不少项目在商用上要授权的。比如那个 Qt,到处告人,“一不小心就会一团糟”。所以咱们这边做上位机、客户端什么的基本用 .NET,Qt 几乎没几个人用,很多公司都不敢用,怕被找上门。

购买授权这种事不要问老周,因为老周也搞不懂。但老周大胆怀疑,这些王X蛋是不是故意的,想通过打官司来发家致富?其实大伙常用的手机 App 表面免费,实际上你如果有心情去阅读一下用户协议,你会冒出一身冷汗。处处给你设坑,你想不跳都不行。比如微信输入法什么的,你如果用它来写小说,那小心,可能你的版权会变成别人的。还有一些不要脸的套壳 AI 服务,在你我不知情的前提下,把咱们的东西变成它们的数据、模型的一部分,产权归谁?科学技术很强大,可有没有质疑过,现在搞科技创新的初心是什么?为了割矮脚白菜?还是为战争服务?还是……

不过,老周从 2010 年至今,接的很多项目 90% 是用 .NET 做的,至少目前没出什么授权上的问题,也没收到过“绿尸寒”。微软还是靠谱的。

为了在数据库中能够兼容,表示选项值的 ObjectValue 属性定义为字符串类型,在数据库也是用文本存放。因为选项值可能是浮点数和日期,但文本很强大,啥值它都能表示。LabelName 属性是显示在用户界面上的,不能被编辑。Required 属性表示此字段值是不是必需的,如果是,会在标签旁边显示一个星号(*)。在绑定时,用 BoolValueConverter 转换器把 bool 值转为控件的 Visibility 属性值,这个转换器是 .NET 类库自有的,不用我们自己写。

ListItems 属性只有当控件是 ComboBox 时才用到,字符串内容,每个项用逗号分隔,显示在下拉列表中。由于它的值是一个字符串,而 ComboBox 的 item source 是列表类型。需要写一个转换器,把字符串分割成一个数组。

public class StrToListConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string list = (string)value;
var splis = list.Split(',').Select(s => s.Trim());
return splis.ToArray();
} public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
}

因为绑定是单向的,所以 ConvertBack 不需要,返回 null 了事。

同样的道理,如果用的是 CheckBox 控件,需要 bool 和 string 之间的转换。

public class BoolValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// 由于数据的值都用字符串表示,所以这里可以直接转成字符串
string strValue = (string)value;
if (bool.TryParse(strValue, out bool val))
{
return val;
}
return false;
} public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// 转回去的时候,都是字符串
return value.ToString();
}
}

由于 CheckBox 控件用于编辑,需要双向绑定,所以 ConvertBack 方法要实现。

步骤二:从TextBox派生一个类

尽管原则上不创作新控件,但由于 TextBox 除输入任意文本外,有些选项只能让用户输入数字。有大伙伴会说,用 InputScope 不就行了吗。还真的不行。input scope 只对触控屏幕键盘有效,对于实体键盘无法限制。因此,为了方便,只好派生出一个类,然后重写 OnPreviewTextInput 方法,把非数字字符排除。其实,处理 TextBox 的 PreviewTextInput 事件也可以的,不过派生一个类来处理,完整性更好,也方便直接把控件代码复制到别的项目使用。Preview开头的事件是【隧道】事件,即从 XAML 树的外层向内引发,而 TextInput 事件是冒泡式的,从内向外引发。键盘事件由 WM_KEY*** 相关消息产生,操作系统会将其传递给窗口。窗口处理了再到里面的控件处理。我们应该处理 Preview 事件,当输入的字符不是数字时,不让它向下传播,这样 TextBox 就不会接收这个字符了。

public class NumberTextBox : TextBox
{
public NumberTextBox()
{
TextNumberType = NumberType.Integer;
// 禁用输入法
InputMethod.SetIsInputMethodEnabled(this, false);
// 设置输入范围是数字,这只对触控键盘有效
InputScope ipscope = new();
ipscope.Names.Add(new InputScopeName(InputScopeNameValue.Number));
this.InputScope = ipscope;
} /// <summary>
/// 获取/设置数字类型
/// </summary>
public NumberType TextNumberType { get; set; } protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
Debug.WriteLine($"Text={e.Text}, ControlText={e.ControlText}");
string pattern = TextNumberType == NumberType.Integer ? "[0-9]+" : "[0-9.]+";
var rgxres = Regex.Match(e.Text, pattern);
if (!rgxres.Success)
{
e.Handled = true;
return;
}
base.OnPreviewTextInput(e);
}
} public enum NumberType
{
/// <summary>
/// 整数
/// </summary>
Integer,
/// <summary>
/// 浮点数
/// </summary>
Floating
}

老周这里就用正则表达式来匹配,实际上它只匹配一个字符。只要有字符输入,TextInput 相关的事件就会引发,而关联的文本只是刚刚输入的那个字符,而不是 TextBox 中的所有字符。也就是说,这段代码只是限制,并不能验证输入的对不对。其实这代码还不严谨,如果用户已经输入过小数点了,也可能出现输入 N 个小数点的问题。我们也可以优化一下。

protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
……
if (TextNumberType == NumberType.Floating && e.Text == ".")
{
// 看看他输入了几个小数点
TextBox tb = (TextBox)e.OriginalSource;
int index = tb.Text.LastIndexOf('.');
// 索引如果有效,说明之前输入过小数点
if(index > -1)
{
e.Handled= true;
return;
}
} base.OnPreviewTextInput(e);
}

e.Handled = true 会阻止事件向下传播,就能达到阻止输入某些字符的目的。注意,TextInput 相关事件引发时,一般我们只能获取刚输入的字符,要结合 TextBox 现有的文本来分析,就必须从 TextBox.Text 属性获取。上面代码是先判断当前输入的是浮点数,且当前输入的字符是小数点,如果是,就从 TextBox 现有文本中查找小数点。如果找到,说明之前输入过,就不能再输入了。

当然了,限制并不能代替数验证功能。如果需要,还要进一步用代码检查输入的值是否有效。

步骤三:准备一堆数据模板

要用模板选择器筛选要用的数据模板,首先得准备一系列待选模板,放进资源字典中。

<Window.Resources>
<!--转换器-->
<BooleanToVisibilityConverter x:Key="btvCvt" />
<local:BoolValueConverter x:Key="boolValCvt"/>
<local:StrToListConverter x:Key="strToListCvt"/>
<!--下面几个模板是用于选择的-->
<DataTemplate x:Key="selTempAnyTxt">
<TextBox Text="{Binding Path=ObjectValue, Mode=TwoWay}" InputMethod.IsInputMethodEnabled="False"/>
</DataTemplate> <DataTemplate x:Key="selTempNumberInt">
<local:NumberTextBox Text="{Binding Mode=TwoWay, Path=ObjectValue}" TextNumberType="Integer"/>
</DataTemplate> <DataTemplate x:Key="selTempNumberFloat">
<local:NumberTextBox Text="{Binding Mode=TwoWay, Path=ObjectValue}" TextNumberType="Floating"/>
</DataTemplate> <DataTemplate x:Key="selTempList">
<ComboBox ItemsSource="{Binding Path=ListItems, Converter={StaticResource strToListCvt}}"
Text="{Binding Path=ObjectValue, Mode=TwoWay}"
IsReadOnly="True"
IsEditable="True"/>
</DataTemplate> <DataTemplate x:Key="selTempCheckBox">
<CheckBox IsChecked="{Binding Path=ObjectValue,Mode=TwoWay,Converter={StaticResource boolValCvt}}" Content=""/>
</DataTemplate> <DataTemplate x:Key="selTempDatePicker">
<DatePicker Text="{Binding Path=ObjectValue, Mode=TwoWay}" SelectedDateFormat="Short"/>
</DataTemplate>
<!--END-->
……
</Window.Resources>

要定义一堆模板,看着复杂,其实很简单。

步骤四:编写模板选择器

    public class MyTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
OptionItem opt = (OptionItem)item; switch (opt.UIType)
{
case UIElementType.TextBox:
return TextTemplate;
case UIElementType.NumberBoxInt:
return IntNumTemplate;
case UIElementType.NumberBoxFloat:
return FloatNumTemplate;
case UIElementType.DropdownList:
return DropListTemplate;
case UIElementType.CheckBox:
return CheckBoxTemplate;
case UIElementType.DatePicker:
return DatePickerTemplate;
}
return TextTemplate;
} #region 公共属性,用于引用模板
public DataTemplate TextTemplate { get; set; }
public DataTemplate IntNumTemplate { get; set; }
public DataTemplate FloatNumTemplate { get; set; }
public DataTemplate CheckBoxTemplate { get; set#endregion
}

注意那六大属性,待会把选择器放到XAML资源中,可以引用我们前面定义的那些模板。重写 SelectTemplate 方法是核心。根据 UIType 属性来判断要用的模板。这是咱们前面定义选项类时用的枚举。

步骤五:套用

        <!--模板选择器-->
<local:MyTemplateSelector x:Key="tempSelt"
TextTemplate="{StaticResource selTempAnyTxt}"
IntNumTemplate="{StaticResource selTempNumberInt}"
FloatNumTemplate="{StaticResource selTempNumberFloat}"
DropListTemplate="{StaticResource selTempList}"
CheckBoxTemplate="{StaticResource selTempCheckBox}"
DatePickerTemplate="{StaticResource selTempDatePicker}"
/>
<DataTemplate x:Key="itemTemp" DataType="local:OptionItem">
<Grid MinWidth="300" HorizontalAlignment="Center" Margin="0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0" HorizontalAlignment="Right" Margin="0,0,5,0">
<TextBlock>
<Run Text="{Binding Mode=TwoWay, Path=LabelName}"/>
<Run Text=":"/>
</TextBlock>
<TextBlock Visibility="{Binding Required, Converter={StaticResource btvCvt}}" Foreground="Red" FontSize="16">*</TextBlock>
</StackPanel>
<ContentControl Grid.Column="1" Content="{Binding}" ContentTemplateSelector="{StaticResource tempSelt}"/>
</Grid>
</DataTemplate>

Key 为 itemTemp 的模板是给 ItemsControl 控件用的。而前面的那些模板只是给 ContentControl 用的。也就是说,ItemsControl 控件的列表项套用 itemTemp 模板,而 itemTemp 模板里面的 ContentControl 控件又套用前面定义的模板(由模板选择器决定)。

注意,绑定 Required 属性的 TextBlock 只有一个“*”字符,当 Required 属性为true时,该控件显示星号,表示此字段必填。

步骤六:界面布局

在界面上放一个 ItemsControl 控件就能显示各个选项字段信息了。

<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0">
<ItemsControl x:Name="itOpt"
ItemTemplate="{StaticResource itemTemp}"
/>
</ScrollViewer>
<TextBox Grid.Row="1" x:Name="txtDisp"/>
</Grid>

txtDisp 用来每 3 秒显示一次选项类列表,这个只是用来观察绑定有没有双向更新,并无实际用途。

下面是代码部分。

    public partial class MainWindow : Window
{
private ObservableCollection<OptionItem> _optionItems = new();
private DispatcherTimer timer = new DispatcherTimer();
public MainWindow()
{
InitializeComponent();
timer.Interval = TimeSpan.FromSeconds(3);
timer.Tick += OnTimer;
_optionItems.Add(new()
{
Id = 1,
Required = true,
LabelName = "banch",
UIType = UIElementType.NumberBoxInt,
ObjectValue = "3"
});
_optionItems.Add(new()
{
Id = 2,
LabelName = "factor",
UIType = UIElementType.NumberBoxFloat,
ObjectValue = "0.1",
Required = false
});
_optionItems.Add(new()
{
Id = 3,
LabelName = "tcc1",
ObjectValue = "s-i-2",
Required = true,
UIType = UIElementType.TextBox
});
_optionItems.Add(new()
{
Id = 4,
LabelName = "tcc2",
ObjectValue = "k9",
Required = true,
UIType = UIElementType.TextBox
});
_optionItems.Add(new()
{
Id = 5,
LabelName = "wt_mode",
ObjectValue = "nor",
Required = false,
UIType = UIElementType.DropdownList,// 下拉列表
ListItems = "cls,nor,slw,wdw"
});
_optionItems.Add(new()
{
Id = 6,
LabelName = "flash",
UIType = UIElementType.CheckBox,
ObjectValue = "True",
Required = false
});
_optionItems.Add(new()
{
Id = 7,
LabelName = "size",
ObjectValue = "8",
UIType = UIElementType.NumberBoxInt,
Required = false
});
_optionItems.Add(new()
{
Id = 8,
LabelName = "reg_0xD3",
Required = false,
ObjectValue = "29",
UIType = UIElementType.NumberBoxInt
});
// 绑定
itOpt.ItemsSource = _optionItems;
timer.Start();
} private void OnTimer(object sender, EventArgs e)
{
StringBuilder bd = new StringBuilder();
foreach (OptionItem oi in _optionItems)
{
bd.AppendLine($"{oi.LabelName} = {oi.ObjectValue}");
}
txtDisp.Text = bd.ToString();
}
}

DispatchTimer 只是用来周期性更新显示,没其他用途。

运行程序后,你会发现。程序会根据各个选项的设置,生成编辑字段。

是不是很好用呢?现在,把 factor 字段改为 1.5,等3秒钟后,看看有没有更改。

已经更新了。

再试试,把 flash 的 CheckBox 中的勾去掉,看会不会变成 false。

好了,已经有效果了,这样一来,用户爱添加啥参数就随他的便。

【WPF】根据选项值显示不同的编辑控件(使用DataTemplateSelector)的更多相关文章

  1. WPF DataGrid控件中某一列根据另一个文本列的值显示相应的模板控件

    之前做项目的时候需要实现这样一个功能.WPF DataGrid有两列,一列为"更新状态”列,一列为"值"列,如果"更新状态"列的值为“固定值更新”,则 ...

  2. WPF 时间编辑控件的实现(TimeEditer)

    一.前言 有个项目需要用到时间编辑控件,在大量搜索无果后只能自己自定义一个了.MFC中倒是有这个控件,叫CDateTimeCtrl.大概是这个样子: 二.要实现的功能 要实现的功能包含: 编辑时.分. ...

  3. C# Winform 通过FlowLayoutPanel及自定义的编辑控件,实现快速构建C/S版的编辑表单页面

    个人理解,开发应用程序的目的,不论是B/S或是C/S结构类型,无非就是实现可供用户进行查.增.改.删,其中查询用到最多,开发设计的场景也最为复杂,包括但不限于:表格记录查询.报表查询.导出文件查询等等 ...

  4. Winform 通过FlowLayoutPanel及自定义的编辑控件,实现快速构建C/S版的编辑表单页面 z

    http://www.cnblogs.com/zuowj/p/4504130.html 不论是B/S或是C/S结构类型,无非就是实现可供用户进行查.增.改.删,其中查询用到最多,开发设计的场景 也最为 ...

  5. C# 时间控件 竖直进度条 饼图显示 仪表盘 按钮基础控件库

    Prepare 本文将使用一个NuGet公开的组件来实现一些特殊的控件显示,方便大家进行快速的开发系统. 在Visual Studio 中的NuGet管理器中可以下载安装,也可以直接在NuGet控制台 ...

  6. WPF教程十一:简单了解并使用控件模板

    WPF教程十一:简单了解并使用控件模板 这一章梳理控件模板,每个WPF控件都设计成无外观的,但是行为设计上是不允许改变的,比如使用Button的控件时,按钮提供了能被点击的内容,那么自由的改变控件外观 ...

  7. 基于MVC4+EasyUI的Web开发框架经验总结(5)--使用HTML编辑控件CKEditor和CKFinder

    Web开发上有很多HTML的编辑控件,如CKEditor.kindeditor等等,很多都做的很好,本文主要介绍在MVC界面里面,CKEditor的配置和使用.CKEditor的前身是FCKEdito ...

  8. 编辑控件CKEditor和CKFinder

    -使用HTML编辑控件CKEditor和CKFinder Web开发上有很多HTML的编辑控件,如CKEditor.kindeditor等等,很多都做的很好,本文主要介绍在MVC界面里面,CKEdit ...

  9. Qt 编程指南 4 单行编辑控件

    从 Qt 设计师界面可以看到常用的 Qt 文本编辑和浏览控件,包括四个: 其中单行编辑控件 QLineEdit 和 普通文本编辑控件 QPlainTextEdit 都是针对最普通的 C++ 字符串编辑 ...

  10. WPF 自定义ComboBox样式,自定义多选控件

    原文:WPF 自定义ComboBox样式,自定义多选控件 一.ComboBox基本样式 ComboBox有两种状态,可编辑和不可编辑状态.通过设置IsEditable属性可以切换控件状态. 先看基本样 ...

随机推荐

  1. .net 记录http请求

    记录http请求 环境 .net7 一.过滤器(Filter) 这个过程用的的是操作过滤器(ActionFilter) 二. 2.1 继承IAsyncActionFilter 2.2 重写OnActi ...

  2. SpringMVC学习三(静态资源/AJAX功能/乱码问题)

    静态资源的映射 Springmvc完成ajax功能 SpringMVC返回中文到ajax乱码问题解决方式 1.静态资源映射 对于之前web.xml配置文件中的 先做出如下更改,不可写"/*& ...

  3. 【OpenVINO™】使用OpenVINO™ C# API 部署 YOLO-World实现实时开放词汇对象检测

    YOLO-World是一个融合了实时目标检测与增强现实(AR)技术的创新平台,旨在将现实世界与数字世界无缝对接.该平台以YOLO(You Only Look Once)算法为核心,实现了对视频中物体的 ...

  4. MYSQL CONVERT、JSON_EXTRACT函数的使用总结

    一.CONVERT.CONCAT.COUNT函数联合查询 CONVERT()函数用于将值从一种数据类型转换为表达式中指定的另一种数据类型. MySQL还允许它将指定的值从一个字符集转换为另一个字符集. ...

  5. Js实现抽奖转盘,和点击返回某个模块顶部的功能

    最近写了几个转盘抽奖的活动页面: 1.设定旋转的角度: HTML部分:转盘代码: <div class="lottery"> <div class="l ...

  6. 从零在win10上测试whisper、faster-whisper、whisperx在CPU和GPU的各自表现情况

    Anaconda是什么? Anaconda 是一个开源的 Python 发行版本,主要面向数据科学.机器学习和数据分析等领域.它不仅包含了 Python 解释器本身,更重要的是集成了大量的用于科学计算 ...

  7. js文字处理两端展示中间省略号

    点击查看代码 function ellipsisText(longText, displayLength) { // 确保显示长度至少包含省略号的3个字符 if (displayLength < ...

  8. 4G EPS 中的消息类型

    目录 文章目录 目录 消息 MIB(主消息块) SIBs(多个系统消息块) 系统消息的映射和调度 系统信息的更改通知 消息 LTE 的系统消息是蜂窝网络与 UE 互相交互的与 LTE 系统相关的.特殊 ...

  9. 服务器电源管理(Power Management States)

    目录 文章目录 目录 EIST(智能降频技术) 硬件 固件 操作系统 EIST(智能降频技术) EIST 能够根据不同的 OS(操作系统)工作量自动调节 CPU 的电压和频率,以减少耗电量和发热量.它 ...

  10. pageoffice 6 Vue+Springboot磁盘路径打开文档

    本示例关键代码的编写位置 Vue+Springboot 注意 本文中展示的代码均为关键代码,复制粘贴到您的项目中,按照实际的情况,例如文档路径,用户名等做适当修改即可使用. 在正式的项目开发中,用户文 ...