如何使用C#来编写的一个完整字谜游戏的示例代码分享

介绍

字谜游戏,可能你在许多益智书中都曾看到过。试着在电脑上用不同类别的内容写字谜游戏,并且有自定义字词去玩也是很有意思的。

背景

我很早以前使用Turbo C编码游戏,但我丢失了代码。我觉得用C#.NET让它复活将是一件很伟大的事情。该语言在内存、GC、图形方面提供了很多灵活性,而这些是我在使用C语言的时候必须小心处理的。但是在C语言中的明确关注,会让我们学到很多(这就是为什么C语言被称为“上帝的编程语言”的原因)。另一方面,因为C#.NET照顾到了这些,所以我可以专注于其他地方的增强,例如字的方向,重叠,作弊码,计分,加密等。所以在欣赏两种语言的时候需要有一个平衡。

在题目中我之所以说它是“完整的”,原因如下:

1)它有一些类别的预设词。

2)它在加密文件中保存单词和分数,这样就没有人可以篡改文件。如果要篡改,那么它将恢复到预设并从头开始计分。

3)它有作弊码,但作弊会不利于得分,且显然作弊一旦应用会使分数归零。

4)它有一个计分机制。

使用代码

游戏提供以下功能,具体我将在随后的章节中讨论:

1)载入类别和单词:从程序中硬编码的预设中加载单词。然而,如果玩家提供自定义的单词,那么游戏将自动把所有这些(连同预设)存储在文件中并从那里读取。

2)放在网格上:游戏将所有的单词随机地放在18×18的矩阵中。方向可以是水平,垂直,左下和右下,如上图中所示。

3)计分:对于不同类别,分数单独存储。分数的计算方式是单词的长度乘以乘法因子(这里为10)。与此同时,在找到所有的单词之后,剩余时间(乘以乘法因子)也会加到分数中。

4)显示隐藏的单词:如果时间用完之后,玩家依然找不到所有的单词,那么游戏会用不同的颜色显示没找到的单词。

5)作弊码:游戏在游戏板上提作弊码(mambazamba)。作弊码只简单地设置了一整天的时间(86,400秒)。但是,应用作弊码也会应用让此次运行的计分为零的惩罚。

1)载入类别和单词:

载入预设

我们有一个简单的用于持有类别和单词的类:

class WordEntity
{
    public string Category { get; set; }
    public string Word { get; set; }
}

我们有一些预设的类别和单词如下。预设都是管道分隔的,其中每第15个单词是类别名称,后面的单词是该类别中的单词。

private string PRESET_WORDS =
COUNTRIES|BANGLADESH|GAMBIA|AUSTRALIA|ENGLAND|NEPAL|INDIA|PAKISTAN|TANZANIA|SRILANKA|CHINA|CANADA|JAPAN|BRAZIL|ARGENTINA| +
MUSIC|PINKFLOYD|METALLICA|IRONMAIDEN|NOVA|ARTCELL|FEEDBACK|ORTHOHIN|DEFLEPPARD|BEATLES|ADAMS|JACKSON|PARTON|HOUSTON|SHAKIRA| +
...

我们使用加密在文件中写这些单词。所以没有人可以篡改文件。对于加密我使用了一个从这里借鉴的类。使用简单——你需要传递字符串和用于加密的加密密码。对于解密,你需要传递加密的字符串和密码。

如果文件存在,那么我们从那里读取类别和单词,否则我们保存预设(以及玩家自定义的单词)并从预设那里读取。这在下面的代码中完成:

if (File.Exists(FILE_NAME_FOR_STORING_WORDS))   // If words file exists, then read it.
    ReadFromFile();
else
{   // Otherwise create the file and populate from there.
    string EncryptedWords = StringCipher.Encrypt(PRESET_WORDS, ENCRYPTION_PASSWORD);
    using (StreamWriter OutputFile = new StreamWriter(FILE_NAME_FOR_STORING_WORDS))
        OutputFile.Write(EncryptedWords);
    ReadFromFile();
}

ReadFromFile()方法简单地从存储单词的文件中读取。它首先尝试解密从文件读取的字符串。如果失败(由返回的空白字符串确定),它将显示关于问题的一条消息,然后从内置预设重新加载。否则它从字符串读取并将它们分成类别和单词,并把它们放在单词列表中。每第15个词是类别,后续词是该类别下的单词。

string Str = File.ReadAllText(FILE_NAME_FOR_STORING_WORDS);
string[] DecryptedWords = StringCipher.Decrypt(Str, ENCRYPTION_PASSWORD).Split('|');
if (DecryptedWords[0].Equals())  // This means the file was tampered.
{
    MessageBox.Show(The words file was tampered. Any Categories/Words saved by the player will be lost.);
    File.Delete(FILE_NAME_FOR_STORING_WORDS);
    PopulateCategoriesAndWords();   // Circular reference.
    return;
}

string Category = ;

for (int i = 0; i <= DecryptedWords.GetUpperBound(0); i++)
{
    if (i % (MAX_WORDS + 1) == 0)   // Every 15th word is the category name.
    {
        Category = DecryptedWords[i];
        Categories.Add(Category);
    }
    else
    {
        WordEntity Word = new WordEntity();
        Word.Category = Category;
        Word.Word = DecryptedWords[i];
        WordsList.Add(Word);
    }
}

保存玩家的自定义词

游戏可供应由玩家提供的自定义词。设备位于相同的加载窗口。单词应该最少3个字符长,最多10个字符长,并且需要14个单词——不多也不能不少。指示在标签中。另外单词不能是任何其他词的子部分。例如:不能有如’JAPAN’和’JAPANESE’这样两个词,因为前者包含在后者中。

我将简要介绍一下有效性检查。有3个关于最大长度、最小长度和SPACE输入(不允许空格)的即时检查。这通过将我们自定义的处理程序Control_KeyPress添加到单词条目网格的EditingControlShowingevent中来完成。

private void WordsDataGridView_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{    
    e.Control.KeyPress -= new KeyPressEventHandler(Control_KeyPress);
    e.Control.KeyPress += new KeyPressEventHandler(Control_KeyPress);
}

每当用户输入东西时,处理程序就被调用并检查有效性。完成如下:

TextBox tb = sender as TextBox;
if (e.KeyChar == (char)Keys.Enter)
{
    if (tb.Text.Length <= MIN_LENGTH)   // Checking length
    {
        MessageBox.Show(Words should be at least  + MAX_LENGTH +  characters long.);
        e.Handled = true;
        return;
    }
}
if (tb.Text.Length >= MAX_LENGTH)   // Checking length
{
    MessageBox.Show(Word length cannot be more than  + MAX_LENGTH + .);
    e.Handled = true;
    return;
}
if (e.KeyChar.Equals(' '))  // Checking space; no space allowed. Other invalid characters check can be put here instead of the final check on save button click.
{
    MessageBox.Show(No space, please.);
    e.Handled = true;
    return;
}
e.KeyChar = char.ToUpper(e.KeyChar);

最后,在输入所有单词并且用户选择保存和使用自定义单词之后存在有效性检查。首先它检查是否输入了14个单词。然后它遍历所有的14个单词,并检查它们是否有无效字符。同时它也检查重复的单词。检查成功就把单词添加到列表中。最后,提交另一次迭代,以检查单词是否包含在另一个单词中(例如,不能有如’JAPAN’和’JAPANESE’这样的两个单词,因为前者包含在后者中)。通过下面的代码完成:

public bool CheckUserInputValidity(DataGridView WordsDataGridView, List<string> WordsByThePlayer)
{
    if (WordsDataGridView.Rows.Count != MAX_WORDS + 1)
    {
        MessageBox.Show(You need to have  + MAX_WORDS +  words in the list. Please add more.);
        return false;
    }

    char[] NoLettersList = { ':', ';', '@', '\'', '', '{', '}', '[', ']', '|', '\\', '<', '>', '?', ',', '.', '/',
                            '`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', '~', '!', '#', '$',
                            '%', '^', '&', '*', '(', ')', '_', '+'};   //'
    foreach (DataGridViewRow Itm in WordsDataGridView.Rows)
    {
        if (Itm.Cells[0].Value == null) continue;
        if (Itm.Cells[0].Value.ToString().IndexOfAny(NoLettersList) >= 0)
        {
            MessageBox.Show(Should only contain letters. The word that contains something else other than letters is: ' + Itm.Cells[0].Value.ToString() + ');
            return false;
        }
        if (WordsByThePlayer.IndexOf(Itm.Cells[0].Value.ToString()) != -1)
        {
            MessageBox.Show(Can't have duplicate word in the list. The duplicate word is: ' + Itm.Cells[0].Value.ToString() + ');
            return false;
        }
        WordsByThePlayer.Add(Itm.Cells[0].Value.ToString());
    }
    for (int i = 0; i < WordsByThePlayer.Count - 1; i++)    // For every word in the list.
    {
        string str = WordsByThePlayer[i];
        for (int j = i + 1; j < WordsByThePlayer.Count; j++)    // Check existence with every other word starting from the next word
            if (str.IndexOf(WordsByThePlayer[j]) != -1)
            {
                MessageBox.Show(Can't have a word as a sub-part of another word. Such words are: ' + WordsByThePlayer[i] + ' and ' + WordsByThePlayer[j] + ');
                return false;
            }
    }
    return true;
}

玩家的列表与现有单词一起保存,然后游戏板与该类别中的那些单词一起被打开。

2)放在网格上:

在网格上放置单词

单词通过InitializeBoard()方法被放置在网格上。我们在字符矩阵(二维字符数组)WORDS_IN_BOARD中先放置单词。然后我们在网格中映射这个矩阵。遍历所有的单词。每个单词获取随机方向(水平/垂直/左下/右下)下的随机位置。此时,如果我们可视化的话,单词矩阵看起来会有点像下面这样。

放置通过PlaceTheWords()方法完成,获得4个参数——单词方向,单词本身,X坐标和Y坐标。这是一个关键方法,所以我要逐个解释这四个方向。

水平方向

对于整个单词,逐个字符地运行循环。首先它检查这个词是否落在网格之外。如果这是真的,那么它返回到调用过程以生成新的随机位置和方向。

然后,它检查当前字符是否可能与网格上的现有字符重叠。如果发生这种情况,那么检查它是否是相同的字符。如果不是相同的字符,那就返回到调用方法,请求另一个随机位置和方向。

在这两个检查之后,如果放置是一种可能,那么就把单词放置在矩阵中,并且通过方法StoreWordPosition()将列表中的位置和方向存储在WordPositions中。

for (int i = 0, j = PlacementIndex_X; i < Word.Length; i++, j++)               
// First we check if the word can be placed in the array. For this it needs blanks there.
{
    if (j >= GridSize) return false; // Falling outside the grid. Hence placement unavailable.
    if (WORDS_IN_BOARD[j, PlacementIndex_Y] != '\0')
        if (WORDS_IN_BOARD[j, PlacementIndex_Y] != Word[i])   
        // If there is an overlap, then we see if the characters match. If matches, then it can still go there.
        {
            PlaceAvailable = false;
            break;
        }
}
if (PlaceAvailable)
{   // If all the cells are blank, or a non-conflicting overlap is available, then this word can be placed there. So place it.
    for (int i = 0, j = PlacementIndex_X; i < Word.Length; i++, j++)
        WORDS_IN_BOARD[j, PlacementIndex_Y] = Word[i];
    StoreWordPosition(Word, PlacementIndex_X, PlacementIndex_Y, OrientationDecision);
    return true;
}
break;

垂直/左下/右下方向

相同的逻辑适用于为这3个方向找到单词的良好布局。它们在矩阵位置和边界检查的增量/减量方面不同。

在所有的单词被放置在矩阵中之后,FillInTheGaps()方法用随机字母填充矩阵的其余部分。此时窗体打开并触发Paint()事件。在这个事件上,我们绘制最终显示为40×40像素矩形的线。然后我们将我们的字符矩阵映射到board上。

Pen pen = new Pen(Color.FromArgb(255, 0, 0, 0));

ColourCells(ColouredRectangles, Color.LightBlue);
if (FailedRectangles.Count > 0) ColourCells(FailedRectangles, Color.ForestGreen);

// Draw horizontal lines.
for (int i = 0; i <= GridSize; i++)
    e.Graphics.DrawLine(pen, 40, (i + 1) * 40, GridSize * 40 + 40, (i + 1) * 40);

// Draw vertical lines.
for (int i = 0; i <= GridSize; i++)
    e.Graphics.DrawLine(pen, (i + 1) * 40, 40, (i + 1) * 40, GridSize * 40 + 40);

MapArrayToGameBoard();

MapArrayToGameBoard()方法简单地把我们的字符矩阵放在board上。我们使用来自MSDN的绘图代码。这遍历矩阵中的所有字符,将它们放置在40×40矩形的中间,边距调整为10像素。

Graphics formGraphics = CreateGraphics();
Font drawFont = new Font(Arial, 16);
SolidBrush drawBrush = new SolidBrush(Color.Black);
string CharacterToMap;

for (int i = 0; i < GridSize; i++)
    for (int j = 0; j < GridSize; j++)
    {
        if (WORDS_IN_BOARD[i, j] != '\0')
        {
            CharacterToMap =  + WORDS_IN_BOARD[i, j]; //  is needed as a means for conversion of character to string.
            formGraphics.DrawString(CharacterToMap, drawFont, drawBrush, (i + 1) * 40 + 10, (j + 1) * 40 + 10);
        }
    }

单词发现和有效性检查

鼠标点击位置和释放位置存储在点列表中。对鼠标按钮释放事件(GameBoard_MouseUp())调用CheckValidity()方法。同时,当用户在左键按下的同时拖动鼠标时,我们从起始位置绘制一条线到鼠标指针。这在GameBoard_MouseMove()事件中完成。

if (Points.Count > 1)
    Points.Pop();
if (Points.Count > 0)
    Points.Push(e.Location);

// Form top = X = Distance from top, left = Y = Distance from left.
// However mouse location X = Distance from left, Y = Distance from top.

// Need an adjustment to exact the location.
Point TopLeft = new Point(Top, Left);
Point DrawFrom = new Point(TopLeft.Y + Points.ToArray()[0].X + 10, TopLeft.X + Points.ToArray()[0].Y + 80);
Point DrawTo = new Point(TopLeft.Y + Points.ToArray()[1].X + 10, TopLeft.X + Points.ToArray()[1].Y + 80);

ControlPaint.DrawReversibleLine(DrawFrom, DrawTo, Color.Black); // draw new line

单词的有效性在CheckValidity()方法中检查。它通过抓取所有的字母来制定单词,字母通过使用鼠标查看相应的字符矩阵来绘制。然后检查是否真的匹配单词列表中的单词。如果匹配,则通过将单元格着色为浅蓝色并使单词列表中的单词变灰来更新单元格。

以下是抓取行开始和结束位置的代码片段。首先它检查行是否落在边界之外。然后它制定单词并且存储矩阵的坐标。类似地,它检查垂直,左下和右下单词,并尝试相应地匹配。如果这真的匹配,那么我们通过AddCoordinates()方法将临时矩形存储在我们的ColouredRectangles点列表中。

if (Points.Count == 1) return; // This was a doble click, no dragging, hence return.
int StartX = Points.ToArray()[1].X / 40;    // Retrieve the starting position of the line.
int StartY = Points.ToArray()[1].Y / 40;

int EndX = Points.ToArray()[0].X / 40;      // Retrieve the ending position of the line.
int EndY = Points.ToArray()[0].Y / 40;

if (StartX > GridSize || EndX > GridSize || StartY > GridSize || EndY > GridSize || // Boundary checks.
    StartX <= 0 || EndX <= 0 || StartY <= 0 || EndY <= 0)
{
    StatusLabel.Text = Nope!;
    StatusTimer.Start();
    return;
}

StringBuilder TheWordIntended = new StringBuilder();
List<Point> TempRectangles = new List<Point>();
TheWordIntended.Clear();
if (StartY == EndY) // Horizontal line drawn.
    for (int i = StartX; i <= EndX; i++)
    {
        TheWordIntended.Append(WORDS_IN_BOARD[i - 1, StartY - 1].ToString());
        TempRectangles.Add(new Point(i * 40, StartY * 40));
    }

3)计分:

对于计分,我们有计分文件。如果缺少,则使用当前分数和类别创建一个。这里,再次,所有的分数被组合在一个大的管道分隔的字符串中,然后该字符串被加密并放入文件。我们有四个实体。

class ScoreEntity
{
    public string Category { get; set; }
    public string Scorer { get; set; }
    public int Score { get; set; }
    public DateTime ScoreTime { get; set; }
..............
..............

最多允许一个类别14个分数。首先加载分数列表中的所有分数,然后获得当前分类分数的排序子集。在该子集中,检查当前分数是否大于任何可用的分数。如果是,则插入当前分数。之后,检查子集数是否超过14,如果超过了,就消除最后一个。所以最后的得分消失了,列表总是有14个分数。这在CheckAndSaveIfTopScore()方法中完成。

这里,再次,如果有人篡改得分文件,那么它只会开始一个新的得分。不允许篡改。

4)显示隐藏的单词:

如果时间用完了,那么游戏用绿色显示单词。首先,获取玩家找不到的单词。可以是这样的

List<string> FailedWords = new List<string>();
foreach (string Word in WORD_ARRAY)
    if (WORDS_FOUND.IndexOf(Word) == -1)
        FailedWords.Add(Word);

然后,遍历这些失败的单词位置并制定相应的失败的矩阵。最后,它通过无效来调用窗体的paint方法。

foreach (string Word in FailedWords)
{
    WordPosition Pos = WordPositions.Find(p => p.Word.Equals(Word));

    if (Pos.Direction == Direction.Horizontal) // Horizontal word.
        for (int i = Pos.PlacementIndex_X + 1, j = Pos.PlacementIndex_Y + 1, k = 0; k < Pos.Word.Length; i++, k++)
            FailedRectangles.Add(new Point(i * 40, j * 40));
    else if (Pos.Direction == Direction.Vertical) // Vertical word.
        for (int i = Pos.PlacementIndex_X + 1, j = Pos.PlacementIndex_Y + 1, k = 0; k < Pos.Word.Length; j++, k++)
            FailedRectangles.Add(new Point(i * 40, j * 40));
    else if (Pos.Direction == Direction.DownLeft) // Down left word.
        for (int i = Pos.PlacementIndex_Y + 1, j = Pos.PlacementIndex_X + 1, k = 0; k < Pos.Word.Length; i--, j++, k++)
            FailedRectangles.Add(new Point(i * 40, j * 40));
    else if (Pos.Direction == Direction.DownRight) // Down right word.
        for (int i = Pos.PlacementIndex_X + 1, j = Pos.PlacementIndex_Y + 1, k = 0; k < Pos.Word.Length; i++, j++, k++)
            FailedRectangles.Add(new Point(i * 40, j * 40));
}
Invalidate();

5)作弊码:

这是一件小事了。这工作在keyup事件上,这个事件抓取所有的击键到CheatCode变量。实际上,我们合并玩家在游戏窗口上输入的击键,并看看代码是否与我们的CHEAT_CODE(mambazamba)匹配。例如,如果玩家按下“m”和“a”,那么我们在CheatCode变量中将它们保持为’ma’(因为,ma仍然匹配cheatcode模式)。类似地,如果它匹配CHEAT_CODE的模式,则添加连续变量。然而,一旦它不能匹配模式(例如,’mambi’),则重新开始。

最后,如果匹配,则激活作弊码(将剩余时间提高到完整一天,即86,400秒),并应用惩罚。

CheatCode += e.KeyCode.ToString().ToUpper();
if (CHEAT_CODE.IndexOf(CheatCode) == -1)    // Cheat code didn't match with any part of the cheat code.
    CheatCode = ( + e.KeyCode).ToUpper();                         // Hence erase it to start over.
else if (CheatCode.Equals(CHEAT_CODE) && WORDS_FOUND.Count != MAX_WORDS)
{
    Clock.TimeLeft = 86400;                 // Cheat code applied, literally unlimited time. 86400 seconds equal 1 day.
    ScoreLabel.Text = Score: 0;
    StatusLabel.Text = Cheated! Penalty applied!!;
    StatusTimer.Start();
    CurrentScore = 0;
    Invalidate();

这里有趣的是,我们必须使用WordsListView的KeyUp事件而不是窗体。这是因为在加载游戏窗口后,列表框有焦点,而不是窗体。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


项目中经常遇到CSV文件的读写需求,其中的难点主要是CSV文件的解析。本文会介绍CsvHelper、TextFieldParser、正则表达式三种解析CSV文件的方法,顺带也会介绍一下CSV文件的写方法。 CSV文件标准 在介绍CSV文件的读写方法前,我们需要了解一下CSV文件的格式。 文件示例 一
简介 本文的初衷是希望帮助那些有其它平台视觉算法开发经验的人能快速转入Halcon平台下,通过文中的示例开发者能快速了解一个Halcon项目开发的基本步骤,让开发者能把精力完全集中到算法的开发上面。 首先,你需要安装Halcon,HALCON 18.11.0.1的安装包会放在文章末尾。安装包分开发和
这篇文章主要简单记录一下C#项目的dll文件管理方法,以便后期使用。 设置dll路径 参考C#开发奇技淫巧三:把dll放在不同的目录让你的程序更整洁中间的 方法一:配置App.config文件的privatePath : &lt;runtime&gt; &lt;assemblyBinding xml
在C#中的使用JSON序列化及反序列化时,推荐使用Json.NET——NET的流行高性能JSON框架,当然也可以使用.NET自带的 System.Text.Json(.NET5)、DataContractJsonSerializer、JavaScriptSerializer(不推荐)。
事件总线是对发布-订阅模式的一种实现,是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。&#xA;EventBus维护一个事件的字典,发布者、订阅者在事件总线中获取事件实例并执行发布、订阅操作,事件实例负责维护、执行事件处理程序。
通用翻译API的HTTPS 地址为https://fanyi-api.baidu.com/api/trans/vip/translate,使用方法参考通用翻译API接入文档 。&#xA;请求方式可使用 GET 或 POST 方式(Content-Type 请指定为:application/x-www-for
词云”由美国西北大学新闻学副教授、新媒体专业主任里奇·戈登(Rich Gordon)于2006年最先使用,是通过形成“关键词云层”或“关键词渲染”,对文本中出现频率较高的“关键词”的视觉上的突出。词云图过滤掉大量的文本信息,使浏览者只要一眼扫过文本就可以领略文本的主旨。&#xA;网上大部分文章介绍的是使用P
微软在.NET中对串口通讯进行了封装,我们可以在.net2.0及以上版本开发时直接使用SerialPort类对串口进行读写操作。&#xA;为操作方便,本文对SerialPort类做了一些封装,暂时取名为**SerialPortClient**。
简介 管道为进程间通信提供了平台, 管道分为两种类型:匿名管道、命名管道,具体内容参考.NET 中的管道操作。简单来说,匿名管道只能用于本机的父子进程或线程之间,命名管道可用于远程主机或本地的任意两个进程,本文主要介绍命名管道的用法。 匿名管道在本地计算机上提供进程间通信。 与命名管道相比,虽然匿名
目录自定义日志类NLog版本的日志类Serilog版本的日志类 上个月换工作,新项目又要重新搭建基础框架,把日志实现部分单独记录下来方便以后参考。 自定义日志类 代码大部分使用ChatGPT生成,人工进行了测试和优化,主要特点: 线程安全,日志异步写入文件不影响业务逻辑 支持过期文件自动清理,也可自
[TOC] # 原理简介 本文参考[C#/WPF/WinForm/程序实现软件开机自动启动的两种常用方法](https://blog.csdn.net/weixin_42288432/article/details/120059296),将里面中的第一种方法做了封装成**AutoStart**类,使
简介 FTP是FileTransferProtocol(文件传输协议)的英文简称,而中文简称为“文传协议”。用于Internet上的控制文件的双向传输。同时,它也是一个应用程序(Application)。基于不同的操作系统有不同的FTP应用程序,而所有这些应用程序都遵守同一种协议以传输文件。 FTP
使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。 将特性与程序实体相关联后,可以在运行时使用反射这项技术查询特性。&#xA;在 C# 中,通过用方括号 ([]) 将特性名称括起来,并置于应用该特性的实体的声明上方以指定特性。
# 简介 主流的识别库主要有ZXing.NET和ZBar,OpenCV 4.0后加入了QR码检测和解码功能。本文使用的是ZBar,同等条件下ZBar识别率更高,图片和部分代码参考[在C#中使用ZBar识别条形码](https://www.cnblogs.com/w2206/p/7755656.htm
C#中Description特性主要用于枚举和属性,方法比较简单,记录一下以便后期使用。 扩展类DescriptionExtension代码如下: using System; using System.ComponentModel; using System.Reflection; /// &lt;
本文实现一个简单的配置类,原理比较简单,适用于一些小型项目。主要实现以下功能:保存配置到json文件、从文件或实例加载配置类的属性值、数据绑定到界面控件。&#xA;一般情况下,项目都会提供配置的设置界面,很少手动更改配置文件,所以选择以json文件保存配置数据。
前几天用SerialPort类写一个串口的测试程序,关闭串口的时候会让界面卡死。网上大多数方法都是定义2个bool类型的标记Listening和Closing,关闭串口和接受数据前先判断一下。我的方法是DataReceived事件处理程序用this.BeginInvoke()更新界面,不等待UI线程
约束告知编译器类型参数必须具备的功能。 在没有任何约束的情况下,类型参数可以是任何类型。 编译器只能假定 System.Object 的成员,它是任何 .NET 类型的最终基类。 如果客户端代码使用不满足约束的类型,编译器将发出错误。 通过使用 where 上下文关键字指定约束。&#xA;最常用的泛型约束为
protobuf-net是用于.NET代码的基于契约的序列化程序,它以Google设计的“protocol buffers”序列化格式写入数据,适用于大多数编写标准类型并可以使用属性的.NET语言。&#xA;protobuf-net可通过NuGet安装程序包,也可直接访问github下载源码:https:/
工作中经常遇到需要实现TCP客户端或服务端的时候,如果每次都自己写会很麻烦且无聊,使用SuperSocket库又太大了。这时候就可以使用SimpleTCP了,当然仅限于C#语言。&#xA;SimpleTCP是一个简单且非常有用的 .NET 库,用于处理启动和使用 TCP 套接字(客户端和服务器)的重复性任务