- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
@ 。
在Web端常见的有 Quill 、 TinyMCE 这些开源免费的富文本编辑器,而目前 .NET MAUI 方面没有类似的富文本编辑器可以免费使用.
使用.NET MAUI实现一个富文本编辑器并不难,今天就来写一个 。
使用 .NET MAU 实现跨平台支持,本项目可运行于Android、iOS平台。由于篇幅本文只展示Android平台的代码.
.NET MAUI提供了 编辑器 控件,允许输入和编辑多行文本,虽然提供了字号,字体,颜色等控件属性,但我们无法为每个字符设置样式。我们将通过原生控件提供的范围选择器实现这一功能.
.NET MAUI提供了 Handler 的跨平台特性,我们将利用Handler实现所见即所得内容编辑器组件。这篇博文介绍了如何用Handler实现自定义跨平台控件,请阅读 [MAUI程序设计] 用Handler实现自定义跨平台控件 。
在各平台中,我们将使用原生控件实现所见即所得的内容编辑器 。
Android使用SpannableString设置文本的复合样式,可以查看https://www.cnblogs.com/jisheng/archive/2013/01/10/2854088.html 。
iOS使用NSAttributeString设置文本的复合样式,可以参考https://blog.csdn.net/weixin_44544690/article/details/124154949 。
新建.NET MAUI项目,命名 RichTextEditor 。
在Controls目录中创建WysiwygContentEditor,继承自Editor,用于实现所见即所得的内容编辑器 。
构造函数中注册HandlerChanged和HandlerChanging事件 。
public class WysiwygContentEditor : Editor
{
public WysiwygContentEditor()
{
HandlerChanged+=WysiwygContentEditor_HandlerChanged;
HandlerChanging+=WysiwygContentEditor_HandlerChanging;
}
}
在HandlerChanged事件中,获取Handler对象,通过它访问虚拟视图和本机视图.
private void WysiwygContentEditor_HandlerChanged(object sender, EventArgs e)
{
var handler = Handler;
if (handler != null)
{
}
}
android端原生控件为AppCompatEditText,iOS端原生控件为UITextView 。
//Android
var platformView = handler.PlatformView as AppCompatEditText;
//iOS
var platformView = handler.PlatformView as UITextView;
不同平台的代码,通过.Net6的条件编译实现,有关条件编译的详细信息,请参考 官方文档 。这次实现的是Android和iOS平台,所以在代码中条件编译语句如下 。
#if ANDROID
//android codes
...
#endif
#if IOS
//iOS codes
...
#endif
定义StyleType枚举,用于控件可以处理的文本样式更改请求类型.
public enum StyleType
{
underline, italic, bold, backgoundColor, foregroundColor, size
}
以及StyleArgs类,用于传递样式变更请求的参数 。
public class StyleArgs : EventArgs
{
public StyleType Style;
public string Params;
public StyleArgs(StyleType style, string @params = null)
{
Style = style;
Params=@params;
}
}
定义SelectionArgs类,用于传递选择范围变更请求的参数 。
public class SelectionArgs : EventArgs
{
public int Start;
public int End;
public SelectionArgs(int start, int end)
{
Start = start;
End = end;
}
}
定义事件用于各平台本机代码的调用 。
public event EventHandler GetHtmlRequest;
public event EventHandler<string> SetHtmlRequest;
public event EventHandler<StyleArgs> StyleChangeRequested;
public event EventHandler<SelectionArgs> SelectionChangeHandler;
创建StyleChangeRequested的订阅事件以响应样式变更请求,对应不同的样式类型,调用不同的方法实现样式变更.
StyleChangeRequested =new EventHandler<StyleArgs>(
(sender, e) =>
{
var EditableText = platformView.EditableText;
switch (e.Style)
{
case StyleType.underline:
UpdateUnderlineSpans(EditableText);
break;
case StyleType.italic:
UpdateStyleSpans(TypefaceStyle.Italic, EditableText);
break;
case StyleType.bold:
UpdateStyleSpans(TypefaceStyle.Bold, EditableText);
break;
case StyleType.backgoundColor:
UpdateBackgroundColorSpans(EditableText, Microsoft.Maui.Graphics.Color.FromArgb(e.Params));
break;
case StyleType.foregroundColor:
UpdateForegroundColorSpans(EditableText, Microsoft.Maui.Graphics.Color.FromArgb(e.Params));
break;
case StyleType.size:
UpdateAbsoluteSizeSpanSpans(EditableText, int.Parse(e.Params));
break;
default:
break;
}
});
android端使用SelectionStart和SelectionEnd获取选择范围,iOS端使用SelectedRange获取选择范围 。
//Android
int getSelectionStart() => platformView.SelectionStart;
int getSelectionEnd() => platformView.SelectionEnd;
//iOS
NSRange getSelectionRange() => platformView.SelectedRange;
MAUI控件中字号使用FontSize属性单位为逻辑像素,与DPI设置相关联。 在android本机平台中,字号通过为EditableText对象设置AbsoluteSizeSpan实现,代码如下 。
void UpdateAbsoluteSizeSpanSpans(IEditable EditableText, int size)
{
var spanType = SpanTypes.InclusiveInclusive;
EditableText.SetSpan(new AbsoluteSizeSpan(size, true), getSelectionStart(), getSelectionEnd(), spanType);
SetEditableText(EditableText, platformView);
}
Android平台中,字体颜色与背景色通过为EditableText对象设置ForegroundColorSpan和BackgroundColorSpan实现 。
void UpdateForegroundColorSpans(IEditable EditableText, Microsoft.Maui.Graphics.Color color)
{
var spanType = SpanTypes.InclusiveInclusive;
EditableText.SetSpan(new ForegroundColorSpan(color.ToAndroid()), getSelectionStart(), getSelectionEnd(), spanType);
SetEditableText(EditableText, platformView);
}
void UpdateBackgroundColorSpans(IEditable EditableText, Microsoft.Maui.Graphics.Color color)
{
var spanType = SpanTypes.InclusiveInclusive;
EditableText.SetSpan(new BackgroundColorSpan(color.ToAndroid()), getSelectionStart(), getSelectionEnd(), spanType);
SetEditableText(EditableText, platformView);
}
将选择文本选择范围内若包含下划线,则移除下划线,否则添加下划线 。
Android平台中通过为EditableText对象设置UnderlineSpan实现为文本添加下划线,通过RemoveSpan方法可以移除下划线, 。
但选择范围可能已包含下划线片段的一部分,因此移除此下划线片段后,需要重新添加下划线片段,以实现部分移除的效果 。
void UpdateUnderlineSpans(IEditable EditableText)
{
var underlineSpans = EditableText.GetSpans(getSelectionStart(), getSelectionEnd(), Java.Lang.Class.FromType(typeof(UnderlineSpan)));
bool hasFlag = false;
var spanType = SpanTypes.InclusiveInclusive;
foreach (var span in underlineSpans)
{
hasFlag = true;
var spanStart = EditableText.GetSpanStart(span);
var spanEnd = EditableText.GetSpanEnd(span);
var newStart = spanStart;
var newEnd = spanEnd;
var startsBefore = false;
var endsAfter = false;
if (spanStart < getSelectionStart())
{
newStart = getSelectionStart();
startsBefore = true;
}
if (spanEnd > getSelectionEnd())
{
newEnd = getSelectionEnd();
endsAfter = true;
}
EditableText.RemoveSpan(span);
if (startsBefore)
{
EditableText.SetSpan(new UnderlineSpan(), spanStart, newStart, SpanTypes.ExclusiveExclusive);
}
if (endsAfter)
{
EditableText.SetSpan(new UnderlineSpan(), newEnd, spanEnd, SpanTypes.ExclusiveExclusive);
}
}
if (!hasFlag)
{
EditableText.SetSpan(new UnderlineSpan(), getSelectionStart(), getSelectionEnd(), spanType);
}
SetEditableText(EditableText, platformView);
}
Android平台中,字体粗细与斜体通过为EditableText对象设置StyleSpan实现,与设置字体下划线一样,需要处理选择范围内已包含StyleSpan的情况 。
TypefaceStyle提供了Normal、Bold、Italic、BoldItalic四种字体样式,粗体+斜体样式是通过组合实现的,因此需要处理样式叠加问题 。
void UpdateStyleSpans(TypefaceStyle flagStyle, IEditable EditableText)
{
var styleSpans = EditableText.GetSpans(getSelectionStart(), getSelectionEnd(), Java.Lang.Class.FromType(typeof(StyleSpan)));
bool hasFlag = false;
var spanType = SpanTypes.InclusiveInclusive;
foreach (StyleSpan span in styleSpans)
{
var spanStart = EditableText.GetSpanStart(span);
var spanEnd = EditableText.GetSpanEnd(span);
var newStart = spanStart;
var newEnd = spanEnd;
var startsBefore = false;
var endsAfter = false;
if (spanStart < getSelectionStart())
{
newStart = getSelectionStart();
startsBefore = true;
}
if (spanEnd > getSelectionEnd())
{
newEnd = getSelectionEnd();
endsAfter = true;
}
if (span.Style == flagStyle)
{
hasFlag = true;
EditableText.RemoveSpan(span);
EditableText.SetSpan(new StyleSpan(TypefaceStyle.Normal), newStart, newEnd, spanType);
}
else if (span.Style == TypefaceStyle.BoldItalic)
{
hasFlag = true;
EditableText.RemoveSpan(span);
var flagLeft = TypefaceStyle.Bold;
if (flagStyle == TypefaceStyle.Bold)
{
flagLeft = TypefaceStyle.Italic;
}
EditableText.SetSpan(new StyleSpan(flagLeft), newStart, newEnd, spanType);
}
if (startsBefore)
{
EditableText.SetSpan(new StyleSpan(span.Style), spanStart, newStart, SpanTypes.ExclusiveExclusive);
}
if (endsAfter)
{
EditableText.SetSpan(new StyleSpan(span.Style), newEnd, spanEnd, SpanTypes.ExclusiveExclusive);
}
}
if (!hasFlag)
{
EditableText.SetSpan(new StyleSpan(flagStyle), getSelectionStart(), getSelectionEnd(), spanType);
}
SetEditableText(EditableText, platformView);
}
所见即所得的内容需要被序列化和反序列化以便存储或传输,我们仍然使用HTML作为中间语言,好在Android和iOS平台都有HTML互转的对应实现.
在Platform/Android目录下创建HtmlParser.Android作为Android平台序列化和反序列化的实现.
public static class HtmlParser_Android
{
public static ISpanned HtmlToSpanned(string htmlString)
{
ISpanned spanned = Html.FromHtml(htmlString, FromHtmlOptions.ModeCompact);
return spanned;
}
public static string SpannedToHtml(ISpanned spanned)
{
string htmlString = Html.ToHtml(spanned, ToHtmlOptions.ParagraphLinesIndividual);
return htmlString;
}
}
在Platform/iOS目录下创建HtmlParser.iOS作为iOS平台序列化和反序列化的实现.
public static class HtmlParser_iOS
{
static nfloat defaultSize = UIFont.SystemFontSize;
static UIFont defaultFont;
public static NSAttributedString HtmlToAttributedString(string htmlString)
{
var nsString = new NSString(htmlString);
var data = nsString.Encode(NSStringEncoding.UTF8);
var dictionary = new NSAttributedStringDocumentAttributes();
dictionary.DocumentType = NSDocumentType.HTML;
NSError error = new NSError();
var attrString = new NSAttributedString(data, dictionary, ref error);
var mutString = ResetFontSize(new NSMutableAttributedString(attrString));
return mutString;
}
static NSAttributedString ResetFontSize(NSMutableAttributedString attrString)
{
defaultFont = UIFont.SystemFontOfSize(defaultSize);
attrString.EnumerateAttribute(UIStringAttributeKey.Font, new NSRange(0, attrString.Length), NSAttributedStringEnumeration.None, (NSObject value, NSRange range, ref bool stop) =>
{
if (value != null)
{
var oldFont = (UIFont)value;
var oldDescriptor = oldFont.FontDescriptor;
var newDescriptor = defaultFont.FontDescriptor;
bool hasBoldFlag = false;
bool hasItalicFlag = false;
if (oldDescriptor.SymbolicTraits.HasFlag(UIFontDescriptorSymbolicTraits.Bold))
{
hasBoldFlag = true;
}
if (oldDescriptor.SymbolicTraits.HasFlag(UIFontDescriptorSymbolicTraits.Italic))
{
hasItalicFlag = true;
}
if (hasBoldFlag && hasItalicFlag)
{
uint traitsInt = (uint)UIFontDescriptorSymbolicTraits.Bold + (uint)UIFontDescriptorSymbolicTraits.Italic;
newDescriptor = newDescriptor.CreateWithTraits((UIFontDescriptorSymbolicTraits)traitsInt);
}
else if (hasBoldFlag)
{
newDescriptor = newDescriptor.CreateWithTraits(UIFontDescriptorSymbolicTraits.Bold);
}
else if (hasItalicFlag)
{
newDescriptor = newDescriptor.CreateWithTraits(UIFontDescriptorSymbolicTraits.Italic);
}
var newFont = UIFont.FromDescriptor(newDescriptor, defaultSize);
attrString.RemoveAttribute(UIStringAttributeKey.Font, range);
attrString.AddAttribute(UIStringAttributeKey.Font, newFont, range);
}
});
return attrString;
}
public static string AttributedStringToHtml(NSAttributedString attributedString)
{
var range = new NSRange(0, attributedString.Length);
var dictionary = new NSAttributedStringDocumentAttributes();
dictionary.DocumentType = NSDocumentType.HTML;
NSError error = new NSError();
var data = attributedString.GetDataFromRange(range, dictionary, ref error);
var htmlString = new NSString(data, NSStringEncoding.UTF8);
return htmlString;
}
}
在所见即所得编辑器中设置两个方法,一个用于获取编辑器中的内容,一个用于设置编辑器中的内容.
public void SetHtmlText(string htmlString)
{
HtmlString = htmlString;
SetHtmlRequest(this, htmlString);
}
public string GetHtmlText()
{
GetHtmlRequest(this, new EventArgs());
return HtmlString;
}
在HandlerChanged事件方法中的各平台代码段中添加如下代码:
GetHtmlRequest = new EventHandler(
(sender, e) =>
{
var editor = (WysiwygContentEditor)sender;
HtmlString=HtmlParser_Android.SpannedToHtml(platformView.EditableText);
}
);
SetHtmlRequest =new EventHandler<string>(
(sender, htmlString) =>
{
platformView.TextFormatted = HtmlParser_Android.HtmlToSpanned(htmlString);
}
);
在富文本编辑器中的内容,最终会生成一个带有内联样式的HTML字符串,如下所示:
控件由所见即所得编辑器和工具栏组成,所见即所得编辑器用于显示和编辑内容,工具栏用于设置字号、颜色、加粗、斜体、下划线 。
创建RichTextEditor的带有Xaml的ContentView。将所见即所得编辑器放置中央,工具栏放置在底部.
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:RichTextEditor.Controls;assembly=RichTextEditor"
x:Class="RichTextEditor.Controls.RichTextEditor">
<Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<controls:WysiwygContentEditor MinimumHeightRequest="150"
AutoSize="TextChanges"
BackgroundColor="{StaticResource PhoneContrastBackgroundBrush}"
IsSpellCheckEnabled="false"
x:Name="MainEditor"></controls:WysiwygContentEditor>
</Grid>
</Border>
</ContentView>
工具栏内的按钮横向排列 。
<HorizontalStackLayout Grid.Row="3"
Spacing="5"
Margin="0,10">
<Button Text="{Binding Source={x:Reference TextSizeCollectionView}, Path=SelectedItem.Name, FallbackValue=Auto}"
Style="{StaticResource RichTextButtonStyle}"
Clicked="TextSizeButton_Clicked"
x:Name="TextSizeButton"></Button>
<Button Text="Color"
TextColor="{Binding Source={x:Reference ColorCollectionView}, Path=SelectedItem}"
Style="{StaticResource RichTextButtonStyle}"
Clicked="TextColorButton_Clicked"
x:Name="TextColorButton"></Button>
<Button Text="B"
Style="{StaticResource RichTextButtonStyle}"
FontAttributes="Bold"
x:Name="BoldButton"
Clicked="BoldButton_Clicked"></Button>
<Button Text="I"
Style="{StaticResource RichTextButtonStyle}"
FontAttributes="Italic"
x:Name="ItalicButton"
Clicked="ItalicButton_Clicked"></Button>
<Button Text="U"
Style="{StaticResource RichTextButtonStyle}"
FontAttributes="None"
x:Name="UnderLineButton"
Clicked="UnderLineButton_Clicked"></Button>
</HorizontalStackLayout>
配置两个选择器:TextSizeCollectionView为字体大小选择器,ColorCollectionView为字体颜色选择器.
当点击字体大小选择器时,弹出字体大小选择器,当点击字体颜色选择器时,弹出字体颜色选择器.
<VerticalStackLayout x:Name="OptionsLayout"
Grid.Row="2"
Spacing="5">
<CollectionView x:Name="TextSizeCollectionView"
Background="Transparent"
SelectionChanged="TextSizeCollectionView_SelectionChanged"
SelectionMode="Single"
HeightRequest="45">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal"
ItemSpacing="5"></LinearItemsLayout>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Border x:Name="TargetElement"
Style="{StaticResource SelectableLayoutStyle}"
Background="{StaticResource PhoneContrastBackgroundBrush}"
Padding="5,0">
<Label Text="{Binding Name}"
TextColor="{StaticResource PhoneForegroundBrush}"
VerticalOptions="Center"
FontSize="{Binding Value}"></Label>
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<CollectionView x:Name="ColorCollectionView"
SelectionChanged="ColorCollectionView_SelectionChanged"
SelectionMode="Single"
HeightRequest="45">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal"
ItemSpacing="5"></LinearItemsLayout>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<Border x:Name="TargetElement"
Style="{StaticResource SelectableLayoutStyle}"
BackgroundColor="{Binding}"
WidthRequest="40"
HeightRequest="40"
StrokeShape="RoundRectangle 40">
</Border>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</VerticalStackLayout>
后端代码,绑定一些默认值 。
public static List<Color> DefaultTextColorList = new List<Color>() {
Color.FromArgb("#000000"),
Color.FromArgb("#F9371C"),
Color.FromArgb("#F97C1C"),
Color.FromArgb("#F9C81C"),
Color.FromArgb("#41D0B6"),
Color.FromArgb("#2CADF6"),
Color.FromArgb("#6562FC")
};
public static List<TextSize> DefaultTextSizeList = new List<TextSize>() {
new TextSize(){Name="Large", Value=22},
new TextSize(){Name="Middle", Value=18},
new TextSize(){Name="Small", Value=12},
};
效果如下:
在MainPage中使用RichTextEditor,代码如下 。
<controls:RichTextEditor
x:Name="MainRichTextEditor"
Text="{Binding Content}"
Placeholder="{Binding PlaceHolder}"></controls:RichTextEditor>
用 MainRichTextEditor.GetHtmlText() 测试获取富文本编辑器Html序列化功能.
private async void Button_Clicked(object sender, EventArgs e)
{
var html = this.MainRichTextEditor.GetHtmlText();
await DisplayAlert("GetHtml()", html, "OK");
}
我在maui-sample项目中的一些控件,打算做成一个控件库,方便大家使用。控件库地址在下方.
maui-sample项目作为控件库孵化器,代码可能会有点乱,也没有经过严格的测试。当控件完善到一定程度,我会把控件封装起来放到控件库中。如果你有好的控件,欢迎pull request.
maui-sample: Github:maui-samples 。
Mato.Maui控件库 Mato.Maui 。
最后此篇关于[MAUI]写一个跨平台富文本编辑器的文章就讲到这里了,如果你想了解更多关于[MAUI]写一个跨平台富文本编辑器的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我需要(我必须)将大量 float 写入 qdatastream 并且我只使用 4 个字节是必要的。setFloatingPointPrecision 或为 float 和 double 写入 4 或
我有一些 C 代码,我用 Python 对其进行了扩展。扩展的 C 代码有一个将一些结构附加到二进制文件的函数: void writefunction(const struct struct1* so
我正在用 C 语言开发一个小软件,用于在布告栏中读取和写入消息。每条消息都是一个以渐进数字命名的 .txt。 软件是多线程的,有很多用户可以并发操作。 用户可以进行的操作有: 阅读整个公告板(所有 .
我有 2 个线程同时访问同一个大文件 (.txt)。 第一个线程正在从文件中读取。第二个线程正在写入文件。 两个线程都访问同一个 block ,例如(开始:0, block 大小:10),但具有不同的
我做了很多谷歌搜索,但我仍然不确定如何继续。 Linux 下最常见的剪贴板读写方式是什么?我想要同时支持 Gnome 和 KDE 桌面。 更新:我是否认为没有简单的解决方案,必须将多个来源(gnome
1. 定义配置文件信息 有时候我们为了统一管理会把一些变量放到 yml 配置文件中 例如 图片 用 @ConfigurationProperties 代替 @Value 使用方法 定义对应字段的实体
在开始之前,我必须先声明我是 FORTRAN 的新手。我正在维护 1978 年的一段遗留代码。它的目的是从文件中读取一些数据值,处理这些值,然后将处理过的值输出到另一个文本文件。 给定以下 FORTR
我正在制作一个应用程序,我需要存储用户提供的一些信息。我尝试使用 .plist 文件来存储信息,我发现: NSString *filePath = @"/Users/Denis/Documents/X
在delphi类中声明属性时是否可能有不同类型的结果? 示例: 属性月份:字符串读取monthGet(字符串)写入monthSet(整数); 在示例中,我希望在属性(property)月份中,当我:读
我正在以二进制形式将文件加载到数组中,这似乎需要一段时间有没有更好更快更有效的方法来做到这一点。我正在使用类似的方法写回文件。 procedure openfile(fname:string); va
我想实现一个运行模拟的C#控制台应用程序。另外,我想给用户机会在控制台上按“+”或“-”来加速/减速模拟的速度。 有没有办法在编写控制台时读取控制台?我相信我可以为此使用多线程,但是我却不怎么做(我对
这是我的代码: use std::fs::File; use std::io::Write; fn main() { let f = File::create("").unwrap();
我有一个应用程序可以访问 csv 文本文件中的单词。由于它们通常不会更改,因此我将它们放置在 .jar 文件中,并使用 .getResourceAsStream 调用读取它们。我真的很喜欢这种方法,因
我使用kubeadm,docker 17.12.1-ce和法兰绒网络安装了Kubernetes 1.13.1集群 但是,我发现Kubernetes主服务器上有许多空文件,权限为666,该文件允许任何用
我的工作区中有一些 java 文件。现在我想编写一个java程序,它可以读取来自不同源的文本文件,一次一个,一行一行,并将这些行插入到工作区中各自的java文件中。 文本文件会告诉我将哪个文件插入到哪
用户A要求系统读取文件foo,同时用户B想要将他或她的数据保存到同一个文件中。在文件系统级别如何处理这种情况? 最佳答案 大多数文件系统(但不是全部)使用锁定来保护对同一文件的并发访问。锁可以是独占的
我对保护移动应用程序的 firebase 数据库有一些疑问。 例如,在反编译Android应用程序后,黑客可以获取firebase api key 然后访问firebase数据库,这是正确的吗? 假设
我想让文件从外部不可删除,并希望使用java从程序对该文件进行读/写操作。 S0,我使用以下代码使用java创建了不可删除的文件: Process pcs = Runtime.getRunti
当 Selector.select() 以阻塞模式等待读/写操作时,是否可以将写消息推送到客户端?如何将选择器从阻塞模式移至写入模式?触发器可以是一个后台线程,用于放置需要写入给定 channel 的
我目前正在学习在 Linux 环境中使用 C 进行套接字编程。作为一个项目,我正在尝试编写一个基本的聊天服务器和客户端。 目的是让服务器为每个连接的客户端派生一个进程。 我遇到的问题是读取一个 chi
我是一名优秀的程序员,十分优秀!