主頁 > .NET開發 > WPF 簡單聊聊如何使用 DrawGlyphRun 繪制文本

WPF 簡單聊聊如何使用 DrawGlyphRun 繪制文本

2021-10-10 06:04:09 .NET開發

在 WPF 里面,提供的使用底層的方法繪制文本是通過 DrawGlyphRun 的方式,此方法適合用在需要對文本進行精細控制的定制化控制元件上,此方法特別底層而讓呼叫方法比較復雜,本文告訴大家一些簡單的使用方法

本文也屬于 WPF 渲染系列博客,更多渲染相關博客請看 渲染相關

在開始之前,我是來勸退的,如果沒有特別的需求,還是不推薦使用 DrawGlyphRun 的方式進行文本繪制,本文不會告訴大家特別基礎的知識,基礎部分還請看官方檔案: GlyphRun Class (System.Windows.Media)

如果可以的話,順便也將 DirectWrite 的官方檔案也讀一次

使用 DrawGlyphRun 方法之前需要拿到一個 DrawingContext 物件,而在呼叫此方法時,重要的引數是 GlyphRun 物件,此物件包含了大量的引數,本文將來告訴大家這些的引數的用法

例子

新建一個空 WPF 專案用來做例子

在 MainWindow 的 Loaded 事件里面,創建 DrawingVisual 用來獲取 DrawingContext 物件

        public MainWindow()
        {
            InitializeComponent();

            Loaded += MainWindow_Loaded;
        }

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            var drawingVisual = new DrawingVisual();
            using (var drawingContext = drawingVisual.RenderOpen())
            {

            }
            Background = new VisualBrush(drawingVisual);
        }

默認作為 Background 的 Brush 將會被撐開,為了讓后續繪制的文本有指定的尺寸,繪制一個和視窗相同大小的矩形,這樣就可以讓 drawingVisual.Drawing.Bounds 的尺寸和視窗相同

using (var drawingContext = drawingVisual.RenderOpen())
{
    drawingContext.DrawRectangle(Brushes.Black, null, new Rect(0, 0, ActualWidth, ActualHeight));
}

準備

在使用 DrawGlyphRun 繪制需要創建 GlyphRun 物件,需要有以下引數才能構建出繪制的文本內容

  • 字體
  • 字號
  • 文本內容
  • 文本繪制畫刷
  • 文本繪制的坐標

盡管 GlyphRun 物件需要的引數很多,然而很多引數都是可以默認獲取的

字體

在 GlyphRun 里面需要的字體不是 FontFamily 而是需要傳入的是 GlyphTypeface 物件,好在 GlyphTypeface 物件就是可以從 FontFamily 獲取的

每個字體都相當于有一族,多個 Typeface 物件,如下面代碼可以獲取第一個 Typeface 物件

var fontFamily = new FontFamily("微軟雅黑");
Typeface typeface = fontFamily.GetTypefaces().First();

如果此字體是成功安裝的,清真的字體,那么可以通過如下代碼獲取到 GlyphTypeface 物件

bool success = typeface.TryGetGlyphTypeface(out GlyphTypeface glyphTypeface);

大部分字體都能成功拿到,如果不能成功那么,那么就需要自己走字體 Fallback 換個字體啦,或者炸掉,自己決定如果給定的字體創建失敗了,則使用什么字體代替的方法叫做字體 Fallback 演算法

關于如何做字體的回滾策略,還請參閱下文 字體回滾策略 內容

文字編號

每個文字在字體里面都可以有自己的編號,需要通過 CharacterToGlyphMap 獲取對應的值

var text = "林德熙abc123ATdVACC";

List<ushort> glyphIndices = new List<ushort>();

for (var i = 0; i < text.Length; i++)
{
    var c = text[i];
    var glyphIndex = glyphTypeface.CharacterToGlyphMap[c];
    glyphIndices.Add(glyphIndex);
}

需要同時在 GlyphRun 傳入編號和 Unicode 的值

設定字號

在 GlyphRun 里面,支持輸入多個文字和單個文字,在輸入時,可以給每個文字指定字號,字號其實是一個上層的概念,而在 GlyphRun 需要使用底層的文本渲染概念,也就是字符的 AdvanceWidth 的值,簡單的獲取 AdvanceWidth 的方法如下

List<double> advanceWidths = new List<double>();

for (var i = 0; i < text.Length; i++)
{
    var c = text[i];

    var width = glyphTypeface.AdvanceWidths[glyphIndex] * fontSize;
    advanceWidths.Add(width);
}

以上代碼將字串每個文字都設定相同的字號,但是大家可以根據需求,給每個文字都設定字號,對于等寬字符來說,每個字符的 AdvanceWidths 對應的值都應該是相同的,對于非等寬字符,可以在特殊排版需求的時候,強行設定為等寬的值

字符都是等比的,因此只需要設定寬度即可,設定字寬等于設定字號

設定字體偏移

在 GlyphRun 的高級用法里面,是允許設定文字的偏移量,文字的偏移量是一個文字的排版的基礎值,推薦大家寫一點代碼去摸索一下他的規則

List<Point> glyphOffsets = new List<Point>();
var fontSize = 30;

for (var i = 0; i < text.Length; i++)
{
    var c = text[i];

    // 只是決定每個字的偏移量,記得加上 i 乘以哦,字符最好是疊加上 fontSize 的值,使用 fontSize 的倍數
    glyphOffsets.Add(new Point(fontSize * i, 0));
}

在 GlyphRun 里面,文字的偏移量非必須的,可以傳入為空值,因此以上代碼是非必須的,只有需要控制每個字的偏移量的時候才需要用到,此偏移量不是相對坐標值,只是偏移量而已,相對來說比較繞

文本偏移

在 DrawGlyphRun 方法里面是不包含文本的坐標的引數的,需要在 GlyphRun 物件里面設定整個文本的起始坐標,如下面代碼準備好文本的 X 和 Y 坐標值

    var location = new Point(10, 100);

上面代碼只是例子而已,還請替換為你的業務代碼的需要繪制的文本坐標

但是需要知道的是在 GlyphRun 里面傳入的是 BaseLine 而不是 Location 的值,相互轉換的邏輯需要根據 FontFamily 的 Baseline 的值才能計算,代碼如下

        /// <summary>
        /// 獲取指定字體的baseline
        /// </summary>
        /// <param name="fontFamily"></param>
        /// <param name="fontRenderingEmSize"></param>
        /// <returns></returns>
        public static double GetBaseline(this FontFamily fontFamily, double fontRenderingEmSize)
        {
            var baseline = fontFamily.Baseline;

            var renderingEmSize = fontRenderingEmSize;

            var value = https://www.cnblogs.com/lindexi/archive/2021/10/09/baseline * renderingEmSize;
            return value;
        }

        location = new Point(location.X, location.Y + fontFamily.GetBaseline(fontSize));

以上代碼是將 GetBaseline 的回傳值給到 location 的 Y 值,這適合用在水平布局文本上,如果是垂直排版的文本,自然就需要放在水平方向,請根據你的業務代碼修改以上邏輯

語言文化

如果需要支持特殊的文本內容,就需要設定特別的語言文化,默認使用 IetfLanguageTag 即可

                XmlLanguage defaultXmlLanguage =
                    XmlLanguage.GetLanguage(CultureInfo.CurrentUICulture.IetfLanguageTag);

DPI

在新的 GlyphRun 的構造里面要求傳入 DPI 的值用于清晰化顯示,在舊版本的,如 .NET Framework 4.5 版本是不需要的

官方推薦的獲取 DPI 的方法是根據當前文本將要渲染出來的控制元件獲取控制元件的 DPI 的值,通過此方法可以支持多螢屏不同 DPI 的感知,本文提供的方法是獲取主視窗,因為本文的例子是在主視窗繪制文本

    var pixelsPerDip = (float) VisualTreeHelper.GetDpi(Application.Current.MainWindow).PixelsPerDip;

繪制文本

在準備完成之后,即可創建 GlyphRun 用來繪制

  var glyphRun = new GlyphRun
  (
      glyphTypeface,
      bidiLevel: 0,
      isSideways: false,
      renderingEmSize: fontSize,
      pixelsPerDip: pixelsPerDip,   // 只有在高版本的 .NET 才有此引數
      glyphIndices: glyphIndices,
      baselineOrigin: location,     // 設定文本的偏移量
      advanceWidths: advanceWidths, // 設定每個字符的字寬,也就是字號
      glyphOffsets: null,           // 設定每個字符的偏移量,可以為空
      characters: text.ToCharArray(),
      deviceFontName: null,
      clusterMap: null,
      caretStops: null,
      language: defaultXmlLanguage
  );

  drawingContext.DrawGlyphRun(Brushes.White, glyphRun);

請將 Brushes.White 替換為字體前景色的畫刷

以上即可完成文本的繪制,這是一個底層的方式,看起來也很簡單

創建成本

創建一個 GlyphRun 物件的成本有多高?是否需要申請很多資源?其實創建時僅僅只是創建了一個 CLR 物件而已,里面也只有很多的欄位,成本非常低,在創建時不會用到任何非托管的資源,只是一個物件而已

只有在被繪制的時候,才會申請 DirectWrite 的相關資源

獲取幾何物件

通過 BuildGeometry 方法可以從 GlyphRun 物件創建幾何物件,如下面代碼

var geometry = glyphRun.BuildGeometry();

獲取幾何物件可以用此幾何物件做特殊的邏輯,如文字描邊等

需要小心的是呼叫 BuildGeometry 方法是有一定成本的,底層將需要從文本渲染為 Geometry 物件,中間需要經過 MIL 層,建議是能復用就復用,而不要每次都創建

但是在復用時,需要了解的是,不同的字號,創建出來的 Geometry 物件,不一定是相同的,這是為了清晰化顯示的考慮,如字體比較小的時候,將會刪減一些筆畫等

獲取文本的渲染尺寸

可以通過如下代碼獲取文本的渲染尺寸,也可以通過如下方法獲取單個字符的渲染尺寸

  var computeInkBoundingBox = glyphRun.ComputeInkBoundingBox();
  var matrix = new Matrix();
  matrix.Translate(location.X, location.Y);
  computeInkBoundingBox.Transform(matrix);
  //相對于run.BuildGeometry().Bounds方法,run.ComputeInkBoundingBox()會多出一個厚度為1的框框,所以要減去
  if (computeInkBoundingBox.Width >= 2 && computeInkBoundingBox.Height >= 2)
  {
      computeInkBoundingBox.Inflate(-1, -1);
  }

以上的 computeInkBoundingBox 就是文本的繪制的尺寸,相對的坐標是文本的左上角,因此需要通過 location 疊加變換才能讓此矩形和文本渲染重疊

     drawingContext.DrawRectangle(Brushes.Blue, null, computeInkBoundingBox);

文本的渲染尺寸也就是文本的字墨尺寸,此概念是文本排版概念

獲取文本的文字布局尺寸

可以通過以上代碼的 width 獲取文本的字面的布局寬度,而布局高度則需要根據 BaseLine 等屬性獲取,代碼如下

        /// <summary>
        /// 獲取<see cref="GlyphRun"/>的Size
        /// </summary>
        /// <param name="run"></param>
        /// <param name="lineSpacing"></param>
        /// <returns></returns>
        public static Size GetSize(this GlyphRun run, double lineSpacing)
        {
            var renderingEmSize = run.FontRenderingEmSize;
            var height = lineSpacing * renderingEmSize;
            double width = 0;
            foreach (var index in run.GlyphIndices)
            {
                width += run.GlyphTypeface.AdvanceWidths[index];
            }

            width = width * renderingEmSize;
            return new Size(width, height);
        }

呼叫方法是 var glyphSize = glyphRun.GetSize(fontFamily.LineSpacing); 即可拿到文字的布局尺寸

字體回滾策略

字體的回滾策略可以比較佛系,畢竟是找不到字體了,此時就是從已安裝的字體找到一個還能用的字體代替上去

在 WPF 源代碼里面,可以看到底層的 Fallback 字體是 #GLOBAL USER INTERFACE 這個特殊的字體,為了保持和 TextBlock 差不多的邏輯,可以使用如下方法作為字體回滾

    /// <summary>
    /// 用于回滾的字體物件<see cref="FontFamily"/>
    /// </summary>
    public class FallBackFontFamily
    {
        private const string FallBackFontFamilyName = "#GLOBAL USER INTERFACE";
        private FontFamily FallBack { get; } = new FontFamily(FallBackFontFamilyName);

        private FallBackFontFamily(CultureInfo culture)
        {
            FontFamilyItems = FallBack.FamilyMaps
                .Where(map => map.Language == null || map.Language.MatchCulture(culture))
                .Select(map => new FontFamilyMapItem(map)).ToList();
        }

        private IEnumerable<FontFamilyMapItem> FontFamilyItems { get; }

        /// <summary>
        /// 獲取<see cref="FallBackFontFamily"/>物件的單例
        /// </summary>
        public static FallBackFontFamily Instance => FallBackFontFamilyLazy.Value;

        private static readonly Lazy<FallBackFontFamily> FallBackFontFamilyLazy =
            new Lazy<FallBackFontFamily>(() => new FallBackFontFamily(CultureInfo.CurrentCulture));

        /// <summary>
        /// 嘗試獲取fallback的字體名稱
        /// </summary>
        /// <param name="unicodeChar"></param>
        /// <param name="familyName"></param>
        /// <returns></returns>
        public bool TryGetFallBackFontFamily(char unicodeChar, out string familyName)
        {
            var mapItem = FontFamilyItems.FirstOrDefault(item => item.InRange(unicodeChar));
            familyName = null;

            if (mapItem !=null)
            {
                familyName = mapItem.Target;
                return true;
            }
            return false;
        }
    }

以上字體也就是 FontFamily.FontFamilyGlobalUI 屬性的值,請看以下的 WPF 框架源代碼

        internal const string GlobalUI = "#GLOBAL USER INTERFACE";

        internal static FontFamily FontFamilyGlobalUI = new FontFamily(GlobalUI);

默認在 WPF 的 Typeface 創建就包含了此邏輯,請看 Typeface 的源代碼

        public Typeface(
            FontFamily      fontFamily,
            FontStyle       style,
            FontWeight      weight,
            FontStretch     stretch
            )
            : this(
                fontFamily,
                style,
                weight,
                stretch,
                FontFamily.FontFamilyGlobalUI
                )
        {}

因此以上的回滾代碼的意義其實不大,不過可以通過以上代碼添加自己期望的字體回滾串列,如自己在應用程式里面帶了特殊的字體,期望在找不到字體的時候使用自己的字體,就可以使用上面提供的回滾策略代碼,使用方法如下

            if (typeface.TryGetGlyphTypeface(out var glyph))
            {
                // 忽略代碼
            }
            else if (FallBackFontFamily.Instance.TryGetFallBackFontFamily(unicodeChar, out var familyName))
            {
            	// 上面代碼的 unicodeChar 就是傳入的文本的字符
            	// 通過上面代碼可以拿到回滾的字體是否包含此字符的定義
            }
            else
            {
                // 沒有可以支持此字符的字體,那就看業務邏輯的處理啦
            }

代碼

例子

本文所有代碼放在 github 和 gitee 歡迎訪問

可以通過如下方式獲取本文的源代碼,先創建一個空檔案夾,接著使用命令列 cd 命令進入此空檔案夾,在命令列里面輸入以下代碼,即可獲取到本文的代碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 581ea123df0d1067ec1ed3527e8b85edb2fd082e

以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git

獲取代碼之后,進入 NiwejabainelFehargaye 檔案夾

輕文本

實作一個和 TextBox 差很多的單行輕文本最簡代碼如下

    class Foo : UIElement
    {
        public string Text { set; get; } = string.Empty;

        protected override void OnRender(DrawingContext drawingContext)
        {
            var fontFamily = new FontFamily("微軟雅黑");

            var fontSize = 15;
            var y = 0;
            drawingContext.PushOpacity(0.3);
            foreach (var typeface in fontFamily.GetTypefaces().Skip(1).Take(1))
            {
                double offset = 3;

                var baseLine = fontFamily.GetBaseline(fontSize);

                if (typeface.TryGetGlyphTypeface(out var glyphTypeface))
                {
                    foreach (var c in Text)
                    {
                        if (glyphTypeface.CharacterToGlyphMap.TryGetValue(c, out var glyphIndex))
                        {
                            // 在排版,不適合將每個字符的寬度獨立進行計算,有很多字符是需要重疊布局的
                            var width = glyphTypeface.AdvanceWidths[glyphIndex] * fontSize;
                            width = GlyphExtension.RefineValue(width);

#pragma warning disable 618 // 忽略呼叫廢棄建構式
                            var glyphRun = new GlyphRun(
#pragma warning restore 618
                                glyphTypeface,
                                0,
                                false,
                                fontSize,
                                new[] { glyphIndex },
                                new Point(offset, baseLine + y),
                                new[] { width },
                                DefaultGlyphOffsetArray,
                                new char[] { c },
                                null,
                                null,
                                null, DefaultXmlLanguage);

                            drawingContext.DrawLine(new Pen(Brushes.Black, 2), new Point(offset, y), new Point(offset + width, y));

                            drawingContext.DrawGlyphRun(Brushes.Coral, glyphRun);

                            var glyphSize = glyphRun.GetSize(fontFamily.LineSpacing);

                            drawingContext.DrawRectangle(null, new Pen(Brushes.Black, 2), new Rect(new Point(offset, y), glyphSize));

                            // 布局的字符寬度
                            offset += width;
                        }
                    }
                }

                y += fontSize;
            }
            drawingContext.Pop();
        }

        private static readonly Point[] DefaultGlyphOffsetArray = new Point[] { new Point() };

        private static readonly XmlLanguage DefaultXmlLanguage =
            XmlLanguage.GetLanguage(CultureInfo.CurrentUICulture.IetfLanguageTag);
    }

以上代碼只是單個字符進行繪制,用于了解每個字符對應的布局值,也就是如上的 DrawRectangle 繪制的內容

上面代碼的 GetBaseline 等都是輔助方法,可以從本文上面找到代碼,也可以通過如下方式獲取代碼

先創建一個空檔案夾,接著使用命令列 cd 命令進入此空檔案夾,在命令列里面輸入以下代碼,即可獲取到本文的代碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin fe704afdd32edb05005b1f35bcc87dc59c900040

以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git

獲取代碼之后,進入 NiwejabainelFehargaye 檔案夾

博客園博客只做備份,博客發布就不再更新,如果想看最新博客,請到 https://blog.lindexi.com/

知識共享許可協議
本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可,歡迎轉載、使用、重新發布,但務必保留文章署名[林德熙](http://blog.csdn.net/lindexi_gd)(包含鏈接:http://blog.csdn.net/lindexi_gd ),不得用于商業目的,基于本文修改后的作品務必以相同的許可發布,如有任何疑問,請與我[聯系](mailto:[email protected]),

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/307089.html

標籤:.NET技术

上一篇:C# Winform 進度條

下一篇:C# 玩轉MongoDB(二)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more