[C#/WINUI3/.NET8] ComboBox 클래스 : TextSubmitted 이벤트를 사용해 텍스트 입력 처리하기
■ ComboBox 클래스의 TextSubmitted 이벤트를 사용해 텍스트 입력을 처리하는 방법을 보여준다. ※ 비주얼 스튜디오에서 TestProject(Unpackaged) 모드로 빌드한다. ※ TestProject.csproj 프로젝트 파일에서 WindowsPackageType
■ ComboBox 클래스의 TextSubmitted 이벤트를 사용해 텍스트 입력을 처리하는 방법을 보여준다. ※ 비주얼 스튜디오에서 TestProject(Unpackaged) 모드로 빌드한다. ※ TestProject.csproj 프로젝트 파일에서 WindowsPackageType
■ HttpClient 클래스의 GetAsync 메소드를 사용해 파일을 다운로드하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
using System.IO; using System.Net.Http; using System.Threading.Tasks; #region 파일 다운로드하기 (비동기) - DownloadFileAsync(url, filePath) /// <summary> /// 파일 다운로드하기 (비동기) /// </summary> /// <param name="url">URL</param> /// <param name="filePath">파일 경로</param> /// <returns>처리 결과</returns> public async Task<bool> DownloadFileAsync(string url, string filePath) { using HttpClient client = new HttpClient(); try { using HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); using Stream stream = await response.Content.ReadAsStreamAsync(); using FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None); await stream.CopyToAsync(fileStream); return true; } catch(HttpRequestException) { return false; } } #endregion |
■ Python 프로그램을 자동 설치하는 방법을 보여준다. ▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
using System; using System.Diagnostics; using System.IO; using System.Net.Http; using System.Threading.Tasks; namespace TestProject; /// <summary> /// 프로그램 /// </summary> class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 - Main() /// <summary> /// 프로그램 시작하기 /// </summary> /// <returns>태스크</returns> static async Task Main() { string installerURL = "https://www.python.org/ftp/python/3.12.4/python-3.12.4-amd64.exe"; string installerFilePath = @"C:\temp\python_installer.exe"; if(!Directory.Exists(@"C:\temp")) { Directory.CreateDirectory(@"C:\temp"); } Console.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss")}] 파이썬 설치 프로그램 다운로드를 시작합니다."); await DownloadFileAsync(installerURL, installerFilePath); Console.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss")}] 파이썬 설치 프로그램 다운로드를 종료합니다."); Console.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss")}] 파이썬 프로그램 설치를 시작합니다."); ProcessStartInfo processStartInfo = new() { FileName = installerFilePath, Arguments = "/quiet InstallAllUsers=1 PrependPath=1", // 자동 설치를 위한 인자 UseShellExecute = false }; Process process = new() { StartInfo = processStartInfo }; process.Start(); process.WaitForExit(); Console.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss")}] 파이썬 프로그램 설치를 종료합니다."); string pythonFilePath = @"C:\Program Files\Python312\python.exe"; if(File.Exists(pythonFilePath)) { Console.WriteLine("Python이 성공적으로 설치되었습니다."); } else { Console.WriteLine("Python 설치에 실패했습니다."); } Console.WriteLine("프로그램을 종료하기 위해 아무 키나 눌러 주세요."); Console.ReadKey(false); } #endregion #region 파일 다운로드하기 (비동기) - DownloadFileAsync(url, filePath) /// <summary> /// 파일 다운로드하기 (비동기) /// </summary> /// <param name="url">URL</param> /// <param name="filePath">파일 경로</param> /// <returns>처리 결과</returns> private static async Task<bool> DownloadFileAsync(string url, string filePath) { using HttpClient client = new HttpClient(); try { using HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); response.EnsureSuccessStatusCode(); using Stream stream = await response.Content.ReadAsStreamAsync(); using FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None); await stream.CopyToAsync(fileStream); return true; } catch(HttpRequestException) { return false; } } #endregion } |
TestProject.zip
■ HttpClient 클래스를 사용해 OLLAMA에게 LLAMA 3 로컬 LLM 통신하는 방법을 보여준다. (스트림) ▶ RequestMessage.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
using Newtonsoft.Json; namespace TestProject; /// <summary> /// 요청 메시지 /// </summary> public class RequestMessage { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 모델 - Model /// <summary> /// 모델 /// </summary> [JsonProperty("model")] public string Model { get; set; } #endregion #region 프롬프트 - Prompt /// <summary> /// 프롬프트 /// </summary> [JsonProperty("prompt")] public string Prompt { get; set; } #endregion } |
▶ ResponseMessage.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
using Newtonsoft.Json; namespace TestProject; /// <summary> /// 응답 메시지 /// </summary> public class ResponseMessage { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 모델 - Model /// <summary> /// 모델 /// </summary> [JsonProperty("model")] public string Model { get; set; } #endregion #region 생성 시간 - CreateTime /// <summary> /// 생성 시간 /// </summary> [JsonProperty("created_at")] public string CreateTime { get; set; } #endregion #region 응답 - Response /// <summary> /// 응답 /// </summary> [JsonProperty("response")] public string Response { get; set; } #endregion #region 완료 여부 - Done /// <summary> /// 완료 여부 /// </summary> [JsonProperty("done")] public bool Done { get; set; } #endregion } |
▶ APIClient.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
using System.Text; using System.Net.Http.Headers; using Newtonsoft.Json; namespace TestProject; /// <summary> /// API 클라이언트 /// </summary> public class APIClient : IDisposable { //////////////////////////////////////////////////////////////////////////////////////////////////// Event ////////////////////////////////////////////////////////////////////////////////////////// Public #region 메시지 도착시 이벤트 - MessageArrived /// <summary> /// 메시지 도착시 이벤트 /// </summary> public event EventHandler<ResponseMessage> MessageArrived; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 리소스 해제 여부 /// </summary> private bool disposed = false; /// <summary> /// BASE URL /// </summary> private const string BASE_URL = "http://localhost:11434"; /// <summary> /// HTTP 클라이언트 /// </summary> private HttpClient client; /// <summary> /// 품질을 갖는 스트림 미디어 타입 헤더 값 /// </summary> private MediaTypeWithQualityHeaderValue streamMediaTypeWithQualityHeaderValue; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region HTTP 클라이언트 - Client /// <summary> /// HTTP 클라이언트 /// </summary> public HttpClient Client { get { return this.client; } } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region API 클라이언트 - APIClient() /// <summary> /// API 클라이언트 /// </summary> public APIClient() { this.client = new HttpClient(); this.streamMediaTypeWithQualityHeaderValue = new MediaTypeWithQualityHeaderValue("text/event-stream"); } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 채팅 요청하기 (비동기) - RequestChatAsync(requestMessage) /// <summary> /// 채팅 요청하기 (비동기) /// </summary> /// <param name="requestMessage">요청 메시지</param> /// <returns>응답 메시지 태스크</returns> public async Task<List<ResponseMessage>> RequestChatAsync(RequestMessage requestMessage) { if(this.client.DefaultRequestHeaders.Accept.Contains(this.streamMediaTypeWithQualityHeaderValue)) { this.client.DefaultRequestHeaders.Accept.Remove(this.streamMediaTypeWithQualityHeaderValue); } string requestJSON = JsonConvert.SerializeObject(requestMessage); StringContent content = new StringContent(requestJSON, Encoding.UTF8, "application/json"); HttpResponseMessage responseMessage = await this.client.PostAsync($"{BASE_URL}/api/generate", content); responseMessage.EnsureSuccessStatusCode(); string responseString = await responseMessage.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject<List<ResponseMessage>>($"[{responseString}]"); } #endregion #region 채팅 요청하기 (스트림) (비동기) - RequestChatStreamAsync(requestMessage) /// <summary> /// 채팅 요청하기 (스트림) (비동기) /// </summary> /// <param name="requestMessage">요청 메시지</param> /// <returns>스트리밍 메시지 리스트 태스크</returns> public async Task<List<ResponseMessage>> RequestChatStreamAsync(RequestMessage requestMessage) { if(!this.client.DefaultRequestHeaders.Accept.Contains(this.streamMediaTypeWithQualityHeaderValue)) { this.client.DefaultRequestHeaders.Accept.Add(this.streamMediaTypeWithQualityHeaderValue); } string requestJSON = JsonConvert.SerializeObject(requestMessage); StringContent content = new StringContent(requestJSON, Encoding.UTF8, "application/json"); using var request = new HttpRequestMessage(HttpMethod.Post, $"{BASE_URL}/api/generate"); request.Content = content; using HttpResponseMessage httpResponseMessage = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); httpResponseMessage.EnsureSuccessStatusCode(); using Stream stream = await httpResponseMessage.Content.ReadAsStreamAsync(); using StreamReader reader = new StreamReader(stream); List<ResponseMessage> responseMessageList = new List<ResponseMessage>(); while(!reader.EndOfStream) { string line = await reader.ReadLineAsync(); if(string.IsNullOrWhiteSpace(line)) { continue; } ResponseMessage responseMessage = JsonConvert.DeserializeObject<ResponseMessage>(line); responseMessageList.Add(responseMessage); MessageArrived?.Invoke(this, responseMessage); } return responseMessageList; } #endregion #region 리소스 해제하기 - Dispose() /// <summary> /// 리소스 해제하기 /// </summary> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Protected #region 리소스 해제하기 - Dispose(disposing) /// <summary> /// 리소스 해제하기 /// </summary> /// <param name="disposing">리소스 해제 여부</param> protected virtual void Dispose(bool disposing) { if(!this.disposed) { if(disposing) { if(this.client != null) { this.client.Dispose(); } this.client = null; this.disposed = true; } } } #endregion } |
■ HttpClient 클래스를 사용해 OLLAMA에게 LLAMA 3 로컬 LLM 통신하는 방법을 보여준다. ▶ RequestMessage.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
using Newtonsoft.Json; namespace TestProject; /// <summary> /// 요청 메시지 /// </summary> public class RequestMessage { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 모델 - Model /// <summary> /// 모델 /// </summary> [JsonProperty("model")] public string Model { get; set; } #endregion #region 프롬프트 - Prompt /// <summary> /// 프롬프트 /// </summary> [JsonProperty("prompt")] public string Prompt { get; set; } #endregion } |
▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
using System.Text; using Newtonsoft.Json; namespace TestProject; /// <summary> /// 프로그램 /// </summary> class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 - Main() /// <summary> /// 프로그램 시작하기 /// </summary> /// <returns>태스크</returns> static async Task Main() { using HttpClient httpClient = new HttpClient(); httpClient.BaseAddress = new Uri("http://localhost:11434"); RequestMessage requestMessage = new() { Model = "llama3", Prompt = "Hello, how are you?" }; StringContent stringContent = new ( JsonConvert.SerializeObject(requestMessage), Encoding.UTF8, "application/json" ); HttpResponseMessage httpResponseMessage = await httpClient.PostAsync("/api/generate", stringContent); string responseString = await httpResponseMessage.Content.ReadAsStringAsync(); Console.WriteLine(responseString); } #endregion } |
TestProject.zip
■ FontIcon 엘리먼트의 FontFamily/Glyph 속성을 사용해 이모지 아이콘을 표시하는 방법을 보여준다. ※ 비주얼 스튜디오에서 TestProject(Unpackaged) 모드로 빌드한다. ※ TestProject.csproj 프로젝트 파일에서 WindowsPackageType
■ Markdown 클래스의 Parse 정적 메소드를 사용해 마크다운 문자열을 파싱하는 방법을 보여준다. ▶ Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
using Markdig; using Markdig.Syntax; namespace TestProject; /// <summary> /// 프로그램 /// </summary> class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Private #region 프로그램 시작하기 - Main() /// <summary> /// 프로그램 시작하기 /// </summary> static void Main() { #region 마크다운 문자열을 설정한다. string markdownString = @" # 제목 이것은 **Markdown** 문서입니다. - 목록 항목 1 - 목록 항목 2 - 목록 항목 3 ```csharp // 코드 블록 예제 Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); Console.WriteLine(""Hello, World!""); ``` 1. 순서가 있는 항목 1. 순서가 있는 항목 1. 순서가 없는 항목 1. 순서가 없는 항목 1. 순서가 있는 항목 1. 순서가 있는 항목 - 순서가 없는 항목 - 순서가 없는 항목 - 순서가 없는 항목 - 순서가 없는 항목 | 값 | 의미 | 기본값 | |---|:---:|---:| | `static` | 유형(기준) 없음 / 배치 불가능 | `static` | | `relative` | 요소 자신을 기준으로 배치 | | | `absolute` | 위치 상 부모(조상)요소를 기준으로 배치 | | | `fixed` | 브라우저 창을 기준으로 배치 | | | `sticky` | 스크롤 영역 기준으로 배치 | | > 인용문을 작성하세요! >> 중첩된 인용문(nested blockquote)을 만들 수 있습니다. >>> 중중첩 인용문 1 >>> 중중첩 인용문 2 >>> 중중첩 인용문 3 | 값 | 의미 | |---|---| | 버티컬바 출력 | \| | | 인라인 코드 강조 | `\|` | > 인용문 - 남의 말이나 글에서 직접 또는 간접으로 따온 문장. > _(네이버 국어 사전)_ BREAK! ![Prunus](http://www.gstatic.com/webp/gallery/4.jpg) 1. 순서가 있는 항목 1. 순서가 있는 항목 1. 순서가 없는 항목 1. 순서가 없는 항목 1. 순서가 있는 항목 1. 순서가 있는 항목 [샘플 PDF 다운로드](https://drive.google.com/file/d/1P-2-Rui-5lX5v2vIUDtMp_NRr1Fmp3--/view?usp=drive_link) "; #endregion MarkdownPipeline markdownPipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build(); MarkdownDocument markdownDocument = Markdown.Parse(markdownString, markdownPipeline); foreach(Block block in markdownDocument) { MarkdownHelper.EnumerateBlock(block, 0); } } #endregion } |
▶ MarkdownHelper.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
using Markdig.Extensions.Tables; using Markdig.Syntax.Inlines; using Markdig.Syntax; public static class MarkdownHelper { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 블럭 나열하기 - EnumerateBlock(block, level) /// <summary> /// 블럭 나열하기 /// </summary> /// <param name="block">블럭</param> /// <param name="level">레벨</param> public static void EnumerateBlock(Block block, int level) { if(block is Table table) { WriteElementType(block, level, false); Console.Write(" : ["); TableRow firstTableRow = table[0] as TableRow; for(int i = 0; i < firstTableRow.Count; i++) { TableColumnDefinition tableColumnDefinition = table.ColumnDefinitions[i]; if(i > 0) { Console.Write(","); } if(tableColumnDefinition.Alignment.HasValue) { Console.Write($"{tableColumnDefinition.Alignment.Value}"); } else { Console.Write($"{TableColumnAlign.Left}"); } } Console.WriteLine("]"); foreach(Block childBlock in table) { if(childBlock is TableRow tableRow) { EnumerateTableRow(tableRow, level + 1); } else { EnumerateBlock(childBlock, level + 1); } } } else { if(block is LeafBlock) { if(block is ParagraphBlock paragraphBlock) { WriteElementType(block, level, true); EnumerateInline(paragraphBlock.Inline, level + 1); } else if(block is HeadingBlock headingBlock) { WriteElementType(block, level, false); Console.WriteLine($" : {headingBlock.Level}"); EnumerateInline(headingBlock.Inline, level + 1); } else if(block is FencedCodeBlock fencedCodeBlock) { WriteElementType(block, level, false); Console.WriteLine($" : {fencedCodeBlock.Info}"); Console.WriteLine("----------------------------------------------------------------------------------------------------"); foreach(object line in fencedCodeBlock.Lines) { Console.WriteLine(line.ToString()); } Console.WriteLine("----------------------------------------------------------------------------------------------------"); } else { WriteElementType(block, level, true); return; } } else if(block is ContainerBlock containerBlock) { WriteElementType(block, level, true); foreach(Block childBlock in containerBlock) { EnumerateBlock(childBlock, level + 1); } } } } #endregion //////////////////////////////////////////////////////////////////////////////// Private #region 엘리먼트 타입 쓰기 - WriteElementType(inline, level, newLine) /// <summary> /// 엘리먼트 타입 쓰기 /// </summary> /// <param name="inline">인라인</param> /// <param name="level">레벨</param> /// <param name="newLine">개행 여부</param> private static void WriteElementType(Inline inline, int level, bool newLine = true) { int count = level * 4; string padding = count > 0 ? new string(' ', count) : string.Empty; string elementType = $"{padding}{inline.GetType().Name}"; if(newLine) { Console.WriteLine(elementType); } else { Console.Write(elementType); } } #endregion #region 인라인 열거하기 - EnumerateInline(inline, level) /// <summary> /// 인라인 열거하기 /// </summary> /// <param name="inline">인라인</param> /// <param name="level">레벨</param> private static void EnumerateInline(Inline inline, int level) { if(inline is ContainerInline containerInline) { if(containerInline is LinkInline linkInline) { WriteElementType(inline, level, false); string isImage = linkInline.IsImage ? "(이미지)" : "(링크)"; Console.WriteLine($" : {isImage} {linkInline.Url}"); foreach(Inline childInline in containerInline) { EnumerateInline(childInline, level + 1); } } else { WriteElementType(inline, level, true); foreach(Inline childInline in containerInline) { EnumerateInline(childInline, level + 1); } } } else if(inline is LeafInline leafInline) { if(inline is LiteralInline literalInline) { WriteElementType(inline, level, false); Console.WriteLine(" : " + literalInline.Content); } else if(inline is CodeInline codeInline) { WriteElementType(inline, level, false); Console.WriteLine(" : " + codeInline.Content); } else { WriteElementType(inline, level, true); } return; } } #endregion #region 엘리먼트 타입 쓰기 - WriteElementType(block, level, newLine) /// <summary> /// 엘리먼트 타입 쓰기 /// </summary> /// <param name="block">블럭</param> /// <param name="level">레벨</param> /// <param name="newLine">신규 행 여부</param> private static void WriteElementType(Block block, int level, bool newLine = true) { int count = level * 4; string padding = count > 0 ? new string(' ', count) : string.Empty; string elementType = $"{padding}{block.GetType().Name}"; if(newLine) { Console.WriteLine(elementType); } else { Console.Write(elementType); } } #endregion #region 테이블 셀 열거하기 - EnumerateTableCell(tableCell, level) /// <summary> /// 테이블 셀 열거하기 /// </summary> /// <param name="tableCell">테이블 셀</param> /// <param name="level">레벨</param> private static void EnumerateTableCell(TableCell tableCell, int level) { WriteElementType(tableCell, level, true); foreach(Block childBlock in tableCell) { EnumerateBlock(childBlock, level + 1); } } #endregion #region 테이블 행 열거하기 - EnumerateTableRow(tableRow, level) /// <summary> /// 테이블 행 열거하기 /// </summary> /// <param name="tableRow">테이블 행</param> /// <param name="level">레벨</param> private static void EnumerateTableRow(TableRow tableRow, int level) { WriteElementType(tableRow, level, true); foreach(Block childBlock in tableRow) { if(childBlock is TableCell tableCell) { EnumerateTableCell(tableCell, level + 1); } else { EnumerateBlock(childBlock, level + 1); } } } #endregion } |
TestProject.zip
■ MarkdownPipelineBuilder 클래스의 Build 메소드를 사용해 MarkdownPipeline 객체를 만드는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 6 7 |
using Markdig; MarkdownPipeline markdownPipeline = new MarkdownPipelineBuilder() .UseAdvancedExtensions() .Build(); |
■ MarkdownExtensions 클래스의 UseAdvancedExtensions 확장 메소드를 사용해 고급 확장 기능을 활성화하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 |
using Markdig; MarkdownPipelineBuilder markdownPipelineBuilder = new MarkdownPipelineBuilder().UseAdvancedExtensions(); |
■ MarkdownPipelineBuilder 클래스를 사용해 마크다운 파이프라인 빌더 객체를 만드는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 |
using Markdig; MarkdownPipelineBuilder markdownPipelineBuilder = new MarkdownPipelineBuilder(); |
■ HeadingBlock 클래스의 Inline 속성을 사용해 텍스트를 구하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
string markdownString = @" 이것은 **Markdown** 문서입니다. - 목록 항목 1 - 목록 항목 2 - 목록 항목 3 # 제목 1 ## 제목 2 ### 제목 3 #### 제목 4 ##### 제목 5 ###### 제목 6 "; MarkdownDocument markdownDocument = Markdown.Parse(markdownString); foreach(HeadingBlock headingBlock in markdownDocument.Descendants<HeadingBlock>()) { int level = headingBlock.Level; string headingBlockText = GetHeadingBlockText(headingBlock); Console.WriteLine($"레벨 {headingBlock.Level} : {headingBlockText}"); } #region 헤딩 블럭 텍스트 구하기 - GetHeadingBlockText(headingBlock) /// <summary> /// 헤딩 블럭 텍스트 구하기 /// </summary> /// <param name="headingBlock">헤딩 블럭</param> /// <returns>헤딩 블럭 텍스트</returns> public string GetHeadingBlockText(HeadingBlock headingBlock) { StringWriter writer = new StringWriter(); HtmlRenderer renderer = new HtmlRenderer(writer); foreach(Inline inline in headingBlock.Inline) { renderer.Write(inline); } return writer.ToString(); } #endregion |
■ Inline 클래스 및 파생 클래스를 보여준다. ▶ 클래스 상속
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
MarkdownObject (IMarkdownObject) Inline (IInline) ContainerInline (IEnumerable<Inline>) EmphasisInline LinkInline FootnoteLink LeafInline AutolinkInline CodeInline HtmlEntityInline HtmlInline LineBreakInline LiteralInline EmojiInline AbbreviationInline |
※ AbbreviationInline 클래스 : Markdig.Extensions.Abbreviations 네임스페이스 ※ EmojiInline 클래스 : Markdig.Extensions.Emoji
■ MarkdownDocument 클래스의 Descendants<T> 메소드를 사용해 해당 타입의 IEnumerable<T> 객체를 구하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
using System; using Markdig; using Markdig.Syntax; string markdownString = @" 이것은 **Markdown** 문서입니다. - 목록 항목 1 - 목록 항목 2 - 목록 항목 3 # 제목 1 ## 제목 2 ### 제목 3 #### 제목 4 ##### 제목 5 ###### 제목 6 "; MarkdownDocument markdownDocument = Markdown.Parse(markdownString); foreach(HeadingBlock headingBlock in markdownDocument.Descendants<HeadingBlock>()) { Console.WriteLine($"Level {headingBlock.Level}"); } |
■ MarkdownDocument 클래스를 사용해 Block 객체를 계층적으로 나열하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
using System; using Markdig; using Markdig.Syntax; string markdownString = @" # 제목 1 ## 제목 2 ### 제목 3 #### 제목 4 ##### 제목 5 ###### 제목 6 이것은 **Markdown** 문서입니다. - 목록 항목 1 - 목록 항목 2 - 목록 항목 3 "; MarkdownDocument markdownDocument = Markdown.Parse(markdownString); foreach(Block block in markdownDocument) { EnumerateBlock(block); } #region 블럭 나열하기 - EnumerateBlock(block, level) /// <summary> /// 블럭 나열하기 /// </summary> /// <param name="block">블럭</param> /// <param name="level">레벨</param> public void EnumerateBlock(Block block, int level = 0) { string padding = level == 0 ? string.Empty : " ".PadLeft((level - 1) * 4); Console.WriteLine($"{padding}{block.GetType().Name}"); if(block is LeafBlock) { return; } else if(block is ContainerBlock containerBlock) { foreach(Block childBlock in containerBlock) { EnumerateBlock(childBlock, level + 1); } } } #endregion |
■ Block 클래스 및 파생 클래스를 보여준다. ▶ 클래스 상속
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
MarkdownObject (IMarkdownObject) Block (IBlock) ContainerBlock (IList<Block>, IReadOnlyList<Block>) ListBlock ListItemBlock QuoteBlock Table TableRow TableCell LeafBlock CodeBlock FencedCodeBlock (IFencedBlock) MathBlock YamlFrontMatterBlock EmptyBlock HeadingBlock HtmlBlock LinkReferenceDefinition ParagraphBlock ThematicBreakBlock |
※ MathBlock 클래스 : Markdig.Extensions.Mathematics 네임스페이스 ※ Table 클래스 : Markdig.Extensions.Tables
■ Markdown 클래스의 Parse 정적 메소드를 사용해 마크다운 문자열을 파싱한 MarkdownDocument 객체를 구하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 6 7 8 9 10 11 12 |
string markdownString = @" # 제목 1 ## 제목 2 ### 제목 3 #### 제목 4 ##### 제목 5 ###### 제목 6 "; MarkdownDocument markdownDocument = Markdown.Parse(markdownString); |
■ Markdig 누겟을 설치하는 방법을 보여준다. 1. Visual Studio를 실행한다. 2. [도구] / [NuGet 패키지 관리자] / [패키지 관리자 콘솔] 메뉴를 실행한다.
■ CanvasTextFormat 클래스의 GetSystemFontFamilies 정적 메소드를 사용해 언어별 폰트명 리스트를 구하는 방법을 보여준다. ※ 상기 클래스를 사용하기 위해서 Microsoft.Graphics.Win2D 누겟을 설치한다. ※
■ InstalledFontCollection 클래스의 Families 속성을 사용해 한글 폰트명 리스트를 구하는 방법을 보여준다. ※ 상기 클래스를 사용하기 위해서 System.Drawing.Common 누겟을 설치한다. ※ 비주얼
■ x:Bind 태그 확장의 FallbackValue 속성을 사용해 바인딩 실패시 기본값을 설정하는 방법을 보여준다. ▶ 예제 코드 (XAML)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<?xml version="1.0" encoding="utf-8"?> <Page x:Class="TestProject.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" FontFamily="나눔고딕코딩" FontSize="16"> <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="10"> <CheckBox Name="checkBox" HorizontalAlignment="Center" Content="I agree to the terms of service." /> <Button HorizontalAlignment="Center" Padding="10" Content="Submit" IsEnabled="{x:Bind (x:Boolean)checkBox.IsChecked, Mode=OneWay, FallbackValue=False}" /> </StackPanel> </Page> |
■ x:Bind 태그 확장에서 Nullable<bool> 타입을 bool 타입으로 바인딩하는 방법을 보여준다. ※ 비주얼 스튜디오에서 TestProject(Unpackaged) 모드로 빌드한다. ※ TestProject.csproj 프로젝트 파일에서 WindowsPackageType
■ RichEditTextDocument 클래스의 SetText 메소드를 사용해 텍스트를 설정하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using Microsoft.UI.Text; using Microsoft.UI.Xaml.Controls; // ... RichEditBox richEditBox; // ... RichEditTextDocument richEditTextDocument = richEditBox.Document; richEditTextDocument.SetText ( TextSetOptions.None, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam laoreet erat vel massa rutrum, eget mollis massa vulputate. Vivamus semper augue leo, eget faucibus nulla mattis nec. Donec scelerisque lacus at dui ultricies, eget auctor ipsum placerat. Integer aliquet libero sed nisi eleifend, nec rutrum arcu lacinia. Sed a sem et ante gravida congue sit amet ut augue. Donec quis pellentesque urna, non finibus metus. Proin sed ornare tellus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam laoreet erat vel massa rutrum, eget mollis massa vulputate. Vivamus semper augue leo, eget faucibus nulla mattis nec. Donec scelerisque lacus at dui ultricies, eget auctor ipsum placerat. Integer aliquet libero sed nisi eleifend, nec rutrum arcu lacinia. Sed a sem et ante gravida congue sit amet ut augue. Donec quis pellentesque urna, non finibus metus. Proin sed ornare tellus." ); |
■ ITextParagraphFormat 인터페이스의 Alignment 속성을 사용해 문단 정의을 설정하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using Microsoft.UI.Text; using Microsoft.UI.Xaml.Controls; // ... RichEditBox richEditBox; // ... RichEditTextDocument richEditTextDocument = richEditBox.Document; ITextSelection textSelection = richEditTextDocument.Selection; ITextParagraphFormat textParagraphFormat = textSelection.ParagraphFormat; textParagraphFormat.Alignment = ParagraphAlignment.Left; |
■ ITextSelection 인터페이스의 ParagraphFormat 속성을 사용해 ITextParagraphFormat 객체를 구하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
using Microsoft.UI.Text; using Microsoft.UI.Xaml.Controls; // ... RichEditBox richEditBox; // ... RichEditTextDocument richEditTextDocument = richEditBox.Document; ITextSelection textSelection = richEditTextDocument.Selection; ITextParagraphFormat textParagraphFormat = textSelection.ParagraphFormat; |
■ RichEditTextDocument 클래스의 Selection 속성을 사용해 ITextSelection 객체를 구하는 방법을 보여준다. ▶ 예제 코드 (C#)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
using Microsoft.UI.Text; using Microsoft.UI.Xaml.Controls // ... RichEditBox richEditBox; // ... RichEditTextDocument richEditTextDocument = richEditBox.Document; ITextSelection textSelection = richEditTextDocument.Selection; |