<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>내꿈은백발락스타</title>
        <link>https://velog.io/</link>
        <description>안뇽하세용 </description>
        <lastBuildDate>Mon, 09 Sep 2024 06:39:14 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>내꿈은백발락스타</title>
            <url>https://velog.velcdn.com/images/yu_oolong/profile/b4f78fb9-3f35-47c3-82c1-f0c58c2f9e83/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 내꿈은백발락스타. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/yu_oolong" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[예외 발생: 'System.InvalidOperationException'(mscorlib.dll) 컬렉션이 수정되었습니다. 열거 작업이 실행되지 않을 수도 있습니다.]]></title>
            <link>https://velog.io/@yu_oolong/%EC%98%88%EC%99%B8-%EB%B0%9C%EC%83%9D-System.InvalidOperationExceptionmscorlib.dll-%EC%BB%AC%EB%A0%89%EC%85%98%EC%9D%B4-%EC%88%98%EC%A0%95%EB%90%98%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4.-%EC%97%B4%EA%B1%B0-%EC%9E%91%EC%97%85%EC%9D%B4-%EC%8B%A4%ED%96%89%EB%90%98%EC%A7%80-%EC%95%8A%EC%9D%84-%EC%88%98%EB%8F%84-%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@yu_oolong/%EC%98%88%EC%99%B8-%EB%B0%9C%EC%83%9D-System.InvalidOperationExceptionmscorlib.dll-%EC%BB%AC%EB%A0%89%EC%85%98%EC%9D%B4-%EC%88%98%EC%A0%95%EB%90%98%EC%97%88%EC%8A%B5%EB%8B%88%EB%8B%A4.-%EC%97%B4%EA%B1%B0-%EC%9E%91%EC%97%85%EC%9D%B4-%EC%8B%A4%ED%96%89%EB%90%98%EC%A7%80-%EC%95%8A%EC%9D%84-%EC%88%98%EB%8F%84-%EC%9E%88%EC%8A%B5%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Mon, 09 Sep 2024 06:39:14 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p>예외, 에러 해결</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>VisualStudio 2022 / WPF 애플리케이션(.NET Framework 4.7.2)</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<p>코드 작성 후 실행 중 전혀 문제가 없어보이던(라고 생각한) 부분에서 예외가 발생했다. </p>
<blockquote>
<p>예외 발생: &#39;System.InvalidOperationException&#39;(mscorlib.dll) 
컬렉션이 수정되었습니다. 열거 작업이 실행되지 않을 수도 있습니다.</p>
</blockquote>
<p>찾아보니 내 경우는 Collection을 foreach로 접근 중일때 Remove나 Add 등의 동작으로 Collection을 변경하려고 했기 때문에 발생하는 예외라고 한다.</p>
<pre><code class="language-cs">ObservableCollection&lt;string&gt; tempCollection = new ObservableCollection&lt;string&gt;();

foreach (string item in tempCollection) 
{
    ...
}</code></pre>
<p>이 때의 해결 방법은</p>
<ol>
<li>foreach가 아닌 for문으로 순회한다.</li>
<li>해당 Collection의 복사본을 만든다. (ToList())</li>
</ol>
<p>2번의 방법으로 해결했다.</p>
<pre><code class="language-cs">foreach (var item in tempCollection.ToList()) 
{
    ...
}</code></pre>
<p>또는 복사본을 이렇게도 만들 수 있겠다.</p>
<pre><code class="language-cs">ObservableCollection&lt;string&gt; tempCollection = new ObservableCollection&lt;string&gt;();

ObservableCollection&lt;string&gt; tempCopyCollection = new ObservableCollection&lt;string&gt;(tempCollection);</code></pre>
<hr>
<h2 id="4-결과">4. 결과</h2>
<p>문제 해결 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C#] CATIA V5 Automation Selection, Publication Add, Remove ]]></title>
            <link>https://velog.io/@yu_oolong/C-CATIA-V5-Automation</link>
            <guid>https://velog.io/@yu_oolong/C-CATIA-V5-Automation</guid>
            <pubDate>Thu, 11 Jul 2024 07:42:35 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p>ActiveDocument가 PartDocument일때 Axis System의 이름을 변경하거나 Publication을 추가, 제거하는 방법.
<a href="https://velog.io/@yu_oolong/C-WPF-CATIA-%EC%97%B4%EA%B8%B0-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0">이 글</a>에서 실행중인 CATIA를 얻어온 후 시작한다.</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>Visual Studio 2022 / WPF 애플리케이션(.NET Framework 4.7.2)</li>
<li>CATIA V5</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<h3 id="3-1-selection">3-1. Selection</h3>
<p>실행 중인 CATIA에서 열려 있는 PartDocument에서 Product를 찾는다.</p>
<pre><code class="language-cs">PartDocument partDocument = (PartDocument)CATIA_Define.oCATIA.ActiveDocument;
Product product = partDocument.Product;</code></pre>
<p>Selection을 Clear하고 시작한다.</p>
<pre><code class="language-cs">Selection catiaSelect = partDocument.Selection;
catiaSelect.Clear();</code></pre>
<p>Selection에서 사용할 수 있는 메소드로 SelectElement2, SelectElement3, SelectElement4 등이 있다.</p>
<blockquote>
</blockquote>
<p>2는 하나의 객체를 선택할 수 있다.
3은 여러 객체를 선택할 수 있다.
4는 활성화 되지 않은 Document에서 여러 객체를 선택할 수 있다.</p>
<p>하나의 객체를 선택할 것이기 때문에 SelectElement2를 사용한다.
SelectElement2의 인자는 다음과 같다.
<code>SelectElement2(Array iFilterType, ref string iMessage, bool iObjectSelectionBeforeCommandUsePossibility)</code></p>
<pre><code class="language-cs">object[] filter = new object[1];
object selectElement;
selectElement = catiaSelect.SelectElement2(filter, &quot;Select Axis System in which you want to add&quot;, false);
</code></pre>
<p>SelectElement2가 실행되면 CATIA에서 객체를 선택할 수 있는 상태가 된다.</p>
<p>설명을 위해 Axis System을 선택하겠다고 적어두었지만 Axis System외에도 선택이 되기 때문에 선택된 객체가 어떤 타입인지 구분할 수 있어야 한다.</p>
<pre><code class="language-cs">if (selectElement != null)
{
        if (Information.TypeName(catiaSelect.Item(1).Value).Equals(&quot;AxisSystem&quot;))
        {
            .
            .
            .
        }
   }</code></pre>
<p>Selection의 첫 번째 item.Value의 TypeName이 AxisSystem이라는 것을 확인했다면 AxisSystem 타입의 객체에 넣어줄 수 있다.</p>
<pre><code class="language-cs">AxisSystem axisSystem = catiaSelect.Item(1).Value;</code></pre>
<p>axisSystem에서 .get_Name()으로 Feature Name을 얻을 수 있고, .set_Name()으로 Feature Name을 변경할 수 있다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/f3aeb833-e34a-4a86-96a9-5fed19de7766/image.png" alt=""></p>
<h3 id="3-2-publication-추가">3-2. Publication 추가</h3>
<p>Catia의 Tools&gt;Publication에서 Axis System과 연결된 Publication을 추가할 수 있다.</p>
<p>이때 추가된 Publication의 이름은 연결된 Axis System의 이름과 같다.
Axis System의 이름을 변경해도 연결은 끊어지지 않는다. 변경된 Axis System의 이름이 Element에 반영되어있기 때문이다.</p>
<p>Catia의 Visual Basic help의 내용에 따르면 CreateReferenceFromName은 이름으로 Reference를 생성하는 메서드이다.
들어가는 인자의 내용은 경로인데 &quot;/&quot;를 사용하여 구성 요소를 분리하고, 제품 경로는 &quot;/!&quot;을 사용하여 분리한다.
제품 경로에서 매개변수를 분리하려면 &quot;&quot;를 사용하면 된다고 한다.
즉 /! 뒤의 문자열이 Publication의 이름이 된다.</p>
<pre><code class="language-cs">Product product = partDocument.Product;

Reference reference = product.CreateReferenceFromName(product.get_Name() + &quot;/!&quot; + &quot;Axis System.1&quot;);

Publications publications = product.Publications;
publications.Add(&quot;Axis System.1&quot;);
</code></pre>
<p>설명을 위해 간단한 구조의 PartDocument로 작업했기 때문에 CreateRefrenceFromName에 작성한 경로를 product.get_Name() + &quot;/!&quot; + &quot;Axis System.1&quot; 이라고 작성했는데 이 부분은 쓰는 사람마다 다 다를 것이다.</p>
<h3 id="3-3-publication-추가">3-3. Publication 추가</h3>
<p>Publication의 이름을 알면 Publication을 제거할 수 있다.
또는 Publication에 연결된 Axis System의 이름을 알면 Publication을 제거할 수 있다.</p>
<p>다음의 코드는 Publications의 개수만큼 for문을 반복하여
Publication에 연결된 Axis System의 이름에 &quot;Axis System.1&quot;이 포함되어 있으면
그 Publication을 Publications에서 Remove하는 코드이다.</p>
<pre><code class="language-cs">Publications publications = product.Publications;

for (int i = 1; i &lt;= publications.Count; i++)
{
    Publication publication = publications.Item(i);
    Reference reference = (Reference)publication.Valuation;

    if (reference.DisplayName.IndexOf(&quot;Axis System.1&quot;) &gt; 0)
    {
        publications.Remove(publications.Item(i).get_Name());
    }
}</code></pre>
<hr>
<h2 id="4-생각해보기">4. 생각해보기</h2>
<blockquote>
<p>Catia Automation 관련 글은 내가 잘 알아서 쓴다기보다는 정말로 안까먹으려고 작성한다.
구글링을 해도 특히 Automation, Macro 관련해서는 한글로 된 글이 별로 없거나...영어권 자료도 빨라야 19년, 22년...정도여서 난감하다.
하지만 비주얼 베이직으로 작성된 코드를 C#으로 바꾸는 과정은 흥미롭다.
Catia에 대해서도 더 알고싶다. 익숙해지는 그날까지 화이팅~~
혹시나 이 글을 읽었는데 잘 못 적은 어휘나 내용이 있다면 이해와 댓글을 부탁드립니다... </p>
</blockquote>
<hr>
<h2 id="5-참조">5. 참조</h2>
<p>Selection 관련</p>
<ul>
<li><a href="https://www.scripting4v5.com/additional-articles/catia-macro-selection/">https://www.scripting4v5.com/additional-articles/catia-macro-selection/</a></li>
<li><a href="https://catiavbmacro.com/2018/08/26/catia-macro-selection/#google_vignette">https://catiavbmacro.com/2018/08/26/catia-macro-selection/#google_vignette</a></li>
<li><a href="https://www.eng-tips.com/viewthread.cfm?qid=383495">https://www.eng-tips.com/viewthread.cfm?qid=383495</a></li>
</ul>
<p>Publications, Publication 관련</p>
<ul>
<li><a href="https://catiavbmacro.com/2018/08/27/delete-catia-publication-free-macro-code/">https://catiavbmacro.com/2018/08/27/delete-catia-publication-free-macro-code/</a></li>
<li><a href="https://www.eng-tips.com/viewthread.cfm?qid=443869">https://www.eng-tips.com/viewthread.cfm?qid=443869</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C#] CATIA AUTOMATION - CATIA 열기, 실행하기]]></title>
            <link>https://velog.io/@yu_oolong/C-WPF-CATIA-%EC%97%B4%EA%B8%B0-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yu_oolong/C-WPF-CATIA-%EC%97%B4%EA%B8%B0-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 28 Jun 2024 04:11:12 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p>C#을 사용해서 Catia V5용 매크로/프로그램을 MVVM 구조로 작성하기. 
CATIA 열기 또는 실행 방법</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>Visual Studio 2022 / WPF 애플리케이션(.NET Framework 4.7.2)</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<p>MVVM 패턴 구조의 신규 프로젝트를 구성한 후 Catia 관련 작업을 할 class를 새로 추가한다. static 또는 singleton으로...나는 singleton으로 만들어 사용하기로 했다.</p>
<pre><code class="language-cs">    public class CatiaDefine
    {
        private static CATIA_Define _Instance = null;

        public static CATIA_Define GetInstance()
        {
            if (_Instance == null)
            {
                _Instance = new CATIA_Define();
            }
            return _Instance;
        }
    }</code></pre>
<p>INFITF CATIA Application에 대한 Typelib 라이브러리를 추가한다.</p>
<p><strong>CATIA V5 Infinterfaces Object Library</strong></p>
<p>참조&gt;참조 추가
<img src="https://velog.velcdn.com/images/yu_oolong/post/7a15e85d-1138-45f9-acf6-f35cd93877d2/image.png" alt="">
<img src="https://velog.velcdn.com/images/yu_oolong/post/4544c98c-4027-43e0-bb0c-20be784e369b/image.png" alt=""></p>
<p>catia를 변수로 선언한다.</p>
<pre><code class="language-cs">public INFITF.Application catia;
</code></pre>
<p>GetActiveObject는 이미 CATIA가 실행 중인 상태에서의 인스턴스를 가져온다.</p>
<pre><code class="language-cs">catia = (INFITF.Application)System.Runtime.InteropServices.Marshal.GetActiveObject(&quot;CATIA.Application&quot;);</code></pre>
<p>CreateInstance는 새 인스턴스를 생성한다.</p>
<pre><code class="language-cs">catia = (INFITF.Application)Activator.CreateInstance(Type.GetTypeFromProgID(&quot;CATIA.Application&quot;));</code></pre>
<p>메서드로 만들어 컨트롤의 동작이 이루어질 때 실행하게 할 수 있다.</p>
<pre><code class="language-cs">        public void CatiaApp()
        {
            try
            {
                catia = (INFITF.Application)System.Runtime.InteropServices.Marshal.GetActiveObject(&quot;CATIA.Application&quot;);
            }
            catch (Exception ex)
            {
                catia = (INFITF.Application)Activator.CreateInstance(Type.GetTypeFromProgID(&quot;CATIA.Application&quot;));
                catia.Visible = true;
                Console.WriteLine(ex.Message);
            }
        }</code></pre>
<p>이렇게 작성하면 try에서 실행중인 CATIA 인스턴스를 못 가져왔을 경우 catch에서 새로 CATIA를 실행하게 된다.
다만 이미 CATIA가 실행 중일 때 새 CATIA를 실행하는 것이므로 계속해서 새 CATIA를 띄우는 문제가 생긴다.</p>
<hr>
<h2 id="4-재부팅-또는-재실행">4. 재부팅 또는 재실행</h2>
<p>처음 코드를 작성했을 때 </p>
<pre><code>catia = (INFITF.Application)System.Runtime.InteropServices.Marshal.GetActiveObject(&quot;CATIA.Application&quot;);</code></pre><p>이 부분에서 catia = null 상태였다. 인스턴스를 가져오지 못한 것인데 코드의 문제는 아니었고 Visual Studio를 껐다가 다시 켜보거나 컴퓨터의 재부팅을 하면 해결되는 문제였다.</p>
<hr>
<h2 id="5-참조">5. 참조</h2>
<ul>
<li><a href="https://stackoverflow.com/questions/40620334/use-c-sharp-for-catia-v5-automation">https://stackoverflow.com/questions/40620334/use-c-sharp-for-catia-v5-automation</a></li>
<li><a href="http://www.catia-forum.cz/forum/topic.php?topic_id=47">http://www.catia-forum.cz/forum/topic.php?topic_id=47</a></li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C# WPF] Microsoft.Xaml.Behaviors.Wpf ItemsControl로 생성된 컨트롤에 MouseLeftButtonDown Interaction.Behaviors 적용하기]]></title>
            <link>https://velog.io/@yu_oolong/C-WPF-Microsoft.Xaml.Behaviors.Wpf-ItemsControl%EB%A1%9C-%EC%83%9D%EC%84%B1%EB%90%9C-%EC%BB%A8%ED%8A%B8%EB%A1%A4%EC%97%90-MouseLeftButtonDown-Interaction.Behaviors-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yu_oolong/C-WPF-Microsoft.Xaml.Behaviors.Wpf-ItemsControl%EB%A1%9C-%EC%83%9D%EC%84%B1%EB%90%9C-%EC%BB%A8%ED%8A%B8%EB%A1%A4%EC%97%90-MouseLeftButtonDown-Interaction.Behaviors-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 27 Jun 2024 00:23:14 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p><a href="https://velog.io/@yu_oolong/C-WPF-OpenCVSharp-FindContours-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%9C%A4%EA%B3%BD%EC%84%A0-%EA%B2%80%EC%B6%9C">지난 글</a>에 이어서 Polyline을 그린 후 클릭 이벤트를 적용하는 방법에 대해 찾아보았고 Microsoft.Xaml.Behaviors.Wpf를 사용하기로 했다. 더불어 <a href="https://velog.io/@yu_oolong/C-WPF-Behavior-InvokeCommandAction">이 글</a>의 댓글에서 지적해주셨던 것 처럼 Behavior의 정석 작성법은 ViewModel에서 작성한 Command를 호출하는 것이 아니라 Behavior&lt;&gt;를 상속받아 OnAttach() 와 OnDetaching()를 작성하는 것이 올바른 방법이기에 이 또한 적용했다.</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>VisualStudio 2022 / WPF 애플리케이션(.NET Framework 4.8)</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<h3 id="3-1-환경-설정">3-1. 환경 설정</h3>
<p>NuGet 패키지 관리에서 Microsoft.Xaml.Behaviors.Wpf 설치
<img src="https://velog.velcdn.com/images/yu_oolong/post/90f3fb15-412b-4d77-b59f-2b246b07e8b5/image.png" alt=""></p>
<p>Behaviors 폴더 생성한 후 MouseBehavior.cs 파일 생성하기.
폴더를 따로 안만들어도 상관 없다. local:로 접근하면 되니까...
<img src="https://velog.velcdn.com/images/yu_oolong/post/e124ce92-66c1-47f8-b352-bdecdaec0ab1/image.png" alt=""></p>
<p>XAML에 네임스페이스를 작성한다.
나는 local이 아니라 bh로 Behavior에 접근하려고 한다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/7e4c5c71-862e-4db2-b547-a310150ce7d4/image.png" alt=""></p>
<h3 id="3-2-behavior-작성">3-2. Behavior 작성</h3>
<p>내가 Behavior를 작성하는 이유는 Polyline에는 Button처럼 마우스 왼쪽 클릭을 했을 때 Command를 적용할 방법이 없기 때문이다.
최대한 MVVM 패턴을 준수하며 작성하고 싶었기 때문에 코드 비하인드를 사용하지 않고 싶었다.</p>
<h4 id="3-2-1-mousebehaviorcs는-behavior를-상속-받는다">3-2-1. MouseBehavior.cs는 Behavior를 상속 받는다.</h4>
<p> <code>: Behavior&lt;T&gt;</code>에서 T는 Behavior를 적용할 Control이 된다. Polyline에 적용하기 때문에 <code>: Behavior&lt;Polyline&gt;</code>이 된다. 
<img src="https://velog.velcdn.com/images/yu_oolong/post/3495ecd5-a2b6-4649-9c43-01a97e4514f5/image.png" alt=""></p>
<h4 id="3-2-2-dependencyproperty를-작성한다">3-2-2. DependencyProperty를 작성한다.</h4>
<pre><code class="language-cs">        public static readonly DependencyProperty MouseXProperty = DependencyProperty.Register(
            &quot;MouseX&quot;, typeof(double), typeof(MouseBehavior), new PropertyMetadata(default(double)));

        public static readonly DependencyProperty MouseYProperty = DependencyProperty.Register(
            &quot;MouseY&quot;, typeof(double), typeof(MouseBehavior), new PropertyMetadata(default(double)));</code></pre>
<p>MouseX, MouseY 라는 DependencyProperty를 추가하면 XAML의 Behavior 작성시 속성으로 사용할 수 있게 된다.</p>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/e4a47bce-1950-4186-8779-311254cc31ed/image.png" alt=""></p>
<h4 id="3-2-3-onattached와-ondetaching의-작성">3-2-3. OnAttached()와 OnDetaching()의 작성</h4>
<p>코드 비하인드에서 Mouse 동작과 관련한 이벤트핸들러를 추가할 때에는 +=연산자를, 삭제할 때에는 -=를 사용한 적이 있는데 그것과 같다.</p>
<pre><code class="language-cs">        protected override void OnAttached()
        {
            AssociatedObject.MouseLeftButtonDown += AssociatedObjectOnMouseLeftButtonDown;
        }
        protected override void OnDetaching()
        {
            AssociatedObject.MouseLeftButtonDown -= AssociatedObjectOnMouseLeftButtonDown;
        }
</code></pre>
<p>이벤트핸들러의 동작을 작성한다. MouseLeftButtonDown 동작이 실행되면 현재 마우스 Position을 가져온다.</p>
<pre><code class="language-cs">        private void AssociatedObjectOnMouseLeftButtonDown(object sender, MouseEventArgs mouseEventArgs)
        {
            var mousePoint = mouseEventArgs.GetPosition(AssociatedObject);
            MouseX = mousePoint.X;
            MouseY = mousePoint.Y;
        }</code></pre>
<h3 id="3-3-viewmodel-작성">3-3. ViewModel 작성</h3>
<p><a href="https://velog.io/@yu_oolong/C-WPF-OpenCVSharp-FindContours-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%9C%A4%EA%B3%BD%EC%84%A0-%EA%B2%80%EC%B6%9C">지난 글</a>에서의 ViewModel에 바인딩 할 PanelX, PanelY를 추가한다.</p>
<pre><code class="language-cs">        private double _panelX;
        public double PanelX
        {
            get { return _panelX; }
            set
            {
                _panelX = value;
                OnPropertyChanged(nameof(PanelX));
            }
        }

        private double _panelY;
        public double PanelY
        {
            get { return _panelY; }
            set
            {
                _panelY = value;
                OnPropertyChanged(nameof(PanelY));
            }
        }</code></pre>
<h3 id="3-4-xaml-작성">3-4. XAML 작성</h3>
<p><a href="https://velog.io/@yu_oolong/C-WPF-OpenCVSharp-FindContours-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%9C%A4%EA%B3%BD%EC%84%A0-%EA%B2%80%EC%B6%9C">지난 글</a>에서의 Xaml에 Behavior를 추가한다.</p>
<pre><code class="language-xaml">&lt;Polyline Points=&quot;{Binding}&quot; Stroke=&quot;Red&quot; Fill=&quot;Transparent&quot; StrokeThickness=&quot;2&quot; Cursor=&quot;Hand&quot;&gt;
    &lt;i:Interaction.Behaviors&gt;
        &lt;bh:MouseBehavior MouseX=&quot;{Binding DataContext.PanelX, RelativeSource={RelativeSource AncestorType=ItemsControl}, Mode=OneWayToSource}&quot;
                          MouseY=&quot;{Binding DataContext.PanelY, RelativeSource={RelativeSource AncestorType=ItemsControl}, Mode=OneWayToSource}&quot; /&gt;
    &lt;/i:Interaction.Behaviors&gt;
&lt;/Polyline&gt;</code></pre>
<p>Mouse의 Position값을 알기 위해 작성한 PanelX, PanelY의 값을 확인하려면 상단에 TextBlock을 추가한다.</p>
<pre><code class="language-xaml">        &lt;Grid Grid.Row=&quot;0&quot; VerticalAlignment=&quot;Bottom&quot;&gt;
            &lt;StackPanel Orientation=&quot;Horizontal&quot; HorizontalAlignment=&quot;Right&quot;&gt;
                &lt;TextBlock Text=&quot;{Binding PanelX, StringFormat=&#39;X={0} &#39;}&quot;/&gt;
                &lt;TextBlock Text=&quot;{Binding PanelY, StringFormat=&#39;Y={0}&#39;}&quot;/&gt;
            &lt;/StackPanel&gt;
        &lt;/Grid&gt;</code></pre>
<hr>
<h2 id="4-결과">4. 결과</h2>
<p>Polyline의 Fill을 Transparent로 설정하고 Cursor를 Hand로 설정하여 마치 Button을 클릭하는 느낌이 됐다.
Polyline이 그려진 부분을 클릭하면 위에 배치한 TextBlock에 바인딩 된 X값과 Y값을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/01111a0b-0873-4c31-85da-15144b8b7d83/image.gif" alt=""></p>
<hr>
<h2 id="5-오류-해결하기">5. 오류 해결하기</h2>
<p>처음엔 Behavior작성 시 PanelX, PanelY를 바인딩 할 때 아래와 같이 작성했었는데 </p>
<pre><code class="language-xaml">&lt;bh:MouseBehavior MouseX=&quot;{Binding Path=DataContext.PanelX, Mode=OneWayToSource, RelativeSource={RelativeSource AncestorType={x:Type Polyline}}}&quot;
                  MouseY=&quot;{Binding Path=DataContext.PanelY, Mode=OneWayToSource, RelativeSource={RelativeSource AncestorType={x:Type Polyline}}}&quot; /&gt;</code></pre>
<p>다음과 같은 에러가 발생했다.</p>
<blockquote>
<p>System.Windows.Data Error: 40 : BindingExpression path error: &#39;PanelX&#39; property not found on &#39;object&#39; &#39;&#39;PointCollection&#39; (HashCode=30046694)&#39;. BindingExpression:Path=DataContext.PanelX; DataItem=&#39;Polyline&#39; (Name=&#39;&#39;); target element is &#39;MouseBehavior&#39; (HashCode=11318800); target property is &#39;MouseX&#39; (type &#39;Double&#39;)</p>
</blockquote>
<p>Console을 보면 Polyline이 그려진 개 수만큼 PanelX, PanelY가 짝으로 에러가 발생하는 것을 알 수 있다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/d2bee0a2-6872-49e1-881a-5ec0d6c0b51c/image.png" alt=""></p>
<p>찾아본 결과 <a href="https://stackoverflow.com/questions/30508649/system-windows-data-error-40-bindingexpression-path-error">stack overflow</a>에서 해결 방법을 찾을 수 있었다.
DataContext의 바인딩 순서가 문제였던 듯 하여 본문의 내용과 같은 순서로 작성했더니 에러가 해결 되었다.</p>
<pre><code class="language-xaml">&lt;bh:MouseBehavior MouseX=&quot;{Binding DataContext.PanelX, RelativeSource={RelativeSource AncestorType=ItemsControl}, Mode=OneWayToSource}&quot;
                  MouseY=&quot;{Binding DataContext.PanelY, RelativeSource={RelativeSource AncestorType=ItemsControl}, Mode=OneWayToSource}&quot; /&gt;</code></pre>
<p>사실 실행을 했을 때는 문제가 없어 보이는데 어쨌든 콘솔에 출력된 에러라면 해결해야하는 것이니까...</p>
<hr>
<h2 id="6-참조">6. 참조</h2>
<p>Behavior 작성</p>
<ul>
<li><a href="https://kaki104.tistory.com/841">https://kaki104.tistory.com/841</a></li>
<li><a href="https://m.blog.naver.com/vactorman/221176866353">https://m.blog.naver.com/vactorman/221176866353</a></li>
<li><a href="https://stackoverflow.com/questions/617312/what-is-a-dependency-property">https://stackoverflow.com/questions/617312/what-is-a-dependency-property</a></li>
<li><a href="https://kaki104.tistory.com/563">https://kaki104.tistory.com/563</a></li>
</ul>
<p>에러 해결</p>
<ul>
<li><a href="https://stackoverflow.com/questions/30508649/system-windows-data-error-40-bindingexpression-path-error">https://stackoverflow.com/questions/30508649/system-windows-data-error-40-bindingexpression-path-error</a></li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C# WPF] OpenCVSharp FindContours 이미지 윤곽선 검출]]></title>
            <link>https://velog.io/@yu_oolong/C-WPF-OpenCVSharp-FindContours-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%9C%A4%EA%B3%BD%EC%84%A0-%EA%B2%80%EC%B6%9C</link>
            <guid>https://velog.io/@yu_oolong/C-WPF-OpenCVSharp-FindContours-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%9C%A4%EA%B3%BD%EC%84%A0-%EA%B2%80%EC%B6%9C</guid>
            <pubDate>Wed, 19 Jun 2024 23:58:32 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p>이미지 처리 할 일이 생겨서... OpenCVSharp 사용법을 찾던 중 기억해둘 내용을 작성한다.</p>
</blockquote>
<hr>
<h2 id="2-환경">2. 환경</h2>
<ul>
<li>VisualStudio 2022 / WPF 애플리케이션(.NET Framework 4.8)</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<h3 id="1-프로젝트-생성-및-환경-설정">1. 프로젝트 생성 및 환경 설정</h3>
<h4 id="1-1-프로젝트-생성">1-1. 프로젝트 생성</h4>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/ee713935-d2be-40cf-b9e0-3e71b79484ac/image.png" alt="">
WPF 앱(.NET Framework)로 프로젝트를 생성한다.
이때 프레임워크는 4.8 이상이어야 한다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/89a86bdf-5662-4a65-aac6-f016749a2807/image.png" alt="">
이유는 WpfExtensions를 설치해야 하는데, 아래 콘솔 내용에 따르면 4.8이상부터 지원하기 때문이다.
아래 이미지는 프레임워크 4.7.2에서 WpfExtensions를 설치하려고 했을 때 콘솔에 출력되는 에러 메시지이다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/e260480f-d840-4c0c-a2ae-399ebb16f14e/image.png" alt=""></p>
<h4 id="1-2-도구nuget-패키지-관리자솔루션용-nuget-패키지-관리">1-2. 도구&gt;NuGet 패키지 관리자&gt;솔루션용 NuGet 패키지 관리...</h4>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/69295ef6-c857-497c-900e-00930f9d657e/image.png" alt="">
OpenCV...를 검색해서 OpenCVSharp4, OpenCVSharp4.runtime.win, OpenCVSharp.WpfExtensions를 설치한다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/b8fb5efd-3227-498a-a179-d9686f00e483/image.png" alt="">
OpenCVSharp4.runtime.win은 Windows에서 작동하기 위한 내부 구현 패키지이고
OpenCVSharp.WpfExtensions는 WPF에서 사용하는 확장 라이브러리이다.</p>
<h4 id="1-3-mvvm-패턴으로-프로젝트-구조-구성">1-3. MVVM 패턴으로 프로젝트 구조 구성</h4>
<p><a href="https://velog.io/@yu_oolong/C-WPF-MVVM-%ED%8C%A8%ED%84%B4%EC%9C%BC%EB%A1%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0">이 글 참고</a></p>
<h3 id="2-control-배치">2. Control 배치</h3>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/a500bb60-2a51-4934-89e3-bbf6de2e2913/image.png" alt=""></p>
<h4 id="2-1-wpf-control-배치">2-1. WPF Control 배치</h4>
<p>Grid를 나눠서 위에는 이미지를 업로드하는 버튼과 없애는 버튼을 배치하고 아래에는 이미지소스를 바인딩할 이미지 컨트롤을 배치한다.
이미지 없애는 버튼은 그렇게 중요하지 않다...</p>
<pre><code class="language-xaml">    &lt;Grid&gt;
        &lt;Grid.RowDefinitions&gt;
            &lt;RowDefinition Height=&quot;50&quot;/&gt;
            &lt;RowDefinition Height=&quot;*&quot; /&gt;
        &lt;/Grid.RowDefinitions&gt;

        &lt;Grid Grid.Row=&quot;0&quot;&gt;
            &lt;StackPanel Orientation=&quot;Horizontal&quot;&gt;
                &lt;Button Command=&quot;{Binding UploadImageCommand}&quot; Cursor=&quot;Hand&quot; Content=&quot;UploadImage&quot; Width=&quot;100&quot; Margin=&quot;10,10,10,10&quot;/&gt;
                &lt;Button Command=&quot;{Binding ClearImageCommand}&quot; Cursor=&quot;Hand&quot; Content=&quot;Clean&quot; Width=&quot;70&quot; Margin=&quot;10,10,10,10&quot; /&gt;
            &lt;/StackPanel&gt;
        &lt;/Grid&gt;

        &lt;Grid Grid.Row=&quot;1&quot; Margin=&quot;10,10,10,10&quot;&gt;
            &lt;Image Source=&quot;{Binding ImageSource}&quot; /&gt;
        &lt;/Grid&gt;
    &lt;/Grid&gt;</code></pre>
<h4 id="2-2-viewmodel에서-command-작성">2-2. ViewModel에서 Command 작성</h4>
<p>xaml의 버튼과 이미지에 바인딩 될 Command를 만들어준다.
UploadImage 버튼을 누르면 파일을 선택할 다이얼로그를 띄우게 한다.</p>
<pre><code class="language-cs">        private BitmapImage _ImageSource;
        public BitmapImage ImageSource
        {
            get { return _ImageSource; }
            set
            {
                _ImageSource = value;
                OnPropertyChanged(nameof(ImageSource));
            }
        }

        public ICommand UploadImageCommand { get; private set; }
        public ICommand ClearImageCommand { get; private set; }

        public MainWindowViewModel() 
        {
            UploadImageCommand = new RelayCommand(UploadImage);
            ClearImageCommand = new RelayCommand(ClearImage);
        }

        private void UploadImage()
        {
            Microsoft.Win32.OpenFileDialog openFileDialog = new Microsoft.Win32.OpenFileDialog();
            openFileDialog.Filter = &quot;Image files (*.jpg, *.jpeg, *.png) | *.jpg; *.jpeg; *.png&quot;;

            if (openFileDialog.ShowDialog() == true)
            {
                string imagePath = openFileDialog.FileName;

                ImageSource = new BitmapImage(new Uri(imagePath));

            }
        }

        private void ClearImage()
        {
            ImageSource = null;
        }</code></pre>
<p>예제에서 사용할 이미지는 FreePik에서 다운로드 받은 무료 이미지이다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/ef08676c-1c9a-4f8e-a37a-85d1b45be612/image.jpg" alt=""></p>
<p>이미지를 업로드하면 이런 화면이 된다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/2bceea45-08c8-4da1-aeb2-b58ef4aaa849/image.png" alt=""></p>
<h3 id="3-opencvsharp">3. OpenCVSharp</h3>
<h4 id="3-1-inrange">3-1. InRange</h4>
<p>InRange는 사용자 지정 Scalar범위 픽셀을 추출한다. 
Scalar(B, G, R) 어두운 색에서-밝은 색까지의 범위 안에서 찾는다.</p>
<pre><code class="language-cs">Cv2.InRange(src, new Scalar(0, 125, 125), new Scalar(100, 255, 255), edge);</code></pre>
<p>new Scalar(0, 125, 125), new Scalar(100, 255, 255)는
다음의 범위이다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/cccc46e4-06d2-4d36-93d0-a90438d00ce4/image.png" alt=""></p>
<h4 id="3-2-findcontours-drawcontours">3-2. FindContours, DrawContours</h4>
<pre><code class="language-cs">Cv2.FindContours(edge, out contours, out hierarchy, RetrievalModes.Tree, ContourApproximationModes.ApproxTC89KCOS);

List&lt;OpenCvSharp.Point[]&gt; new_contours = new 
List&lt;OpenCvSharp.Point[]&gt;();
foreach (OpenCvSharp.Point[] p in contours)
{
    double length = Cv2.ArcLength(p, true);
    if (length &gt; 100)
    {
        new_contours.Add(p);
    }
}

Cv2.DrawContours(dst, new_contours, -1, new Scalar(0, 0, 255), 2, LineTypes.AntiAlias, null, 1);</code></pre>
<h4 id="3-3-imshow와-wpfextensionsbitmapsourceconverter">3-3. ImShow와 WpfExtensions.BitmapSourceConverter</h4>
<pre><code class="language-cs">Cv2.ImShow(&quot;dst&quot;, dst);</code></pre>
<p>WPF의 ImageSource에 바인딩하려면 Mat -&gt; BitmapImage로 바꿀 필요가 있다. 이 때 사용하는 것이 WpfExtensions이다.</p>
<p>WpfExtensions에서 사용할 수 있는 BitmapSourceConverter는 ToBitmapSource라는 Mat -&gt; BitmapSource 메서드가 있다.
BitmapSource로 바뀐 dst 데이터를 다시 BitmapImage로 바꿔주는 작업이 필요하다.</p>
<pre><code class="language-cs">BitmapSource bitmapSource = OpenCvSharp.WpfExtensions.BitmapSourceConverter.ToBitmapSource(dst); // Mat to BitmapSource
ImageSource = ConvertBitmapSourceToBitmapImage(bitmapSource); //BitmapSource to BitmapImage</code></pre>
<p>ConvertBitmapSourceToBitmapImage를 작성한다.</p>
<pre><code class="language-cs">        private BitmapImage ConvertBitmapSourceToBitmapImage(BitmapSource bitmapSource)
        {
            if (!(bitmapSource is BitmapImage bitmapImage))
            {
                bitmapImage = new BitmapImage();

                BmpBitmapEncoder encoder = new BmpBitmapEncoder();
                encoder.Frames.Add(BitmapFrame.Create(bitmapSource));

                using (MemoryStream memoryStream = new MemoryStream())
                {
                    encoder.Save(memoryStream);
                    memoryStream.Position = 0;

                    bitmapImage.BeginInit();
                    bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                    bitmapImage.StreamSource = memoryStream;
                    bitmapImage.EndInit();
                }
            }

            return bitmapImage;
        }</code></pre>
<h3 id="4-getcontour-메서드-작성">4. GetContour 메서드 작성</h3>
<pre><code class="language-cs">        private void GetContour(string imagePath)
        {
            Mat src = new Mat(imagePath); 
            Mat edge = new Mat(); 
            Mat dst = src.Clone();

            OpenCvSharp.Point[][] contours;
            HierarchyIndex[] hierarchy;

            Cv2.InRange(src, new Scalar(0, 125, 125), new Scalar(100, 255, 255), edge); //사용자 지정 Scalar범위 픽셀 추출 Scalar(B, G, R) 어두운 색에서-밝은 색까지의 범위 안에서 찾는다.
            Cv2.FindContours(edge, out contours, out hierarchy, RetrievalModes.Tree, ContourApproximationModes.ApproxTC89KCOS); //Cv2.FindContours(원본 배열, 검출된 윤곽선, 계층 구조, 검색 방법, 근사 방법, 오프셋)

            List&lt;OpenCvSharp.Point[]&gt; new_contours = new List&lt;OpenCvSharp.Point[]&gt;();
            foreach (OpenCvSharp.Point[] p in contours)
            {
                double length = Cv2.ArcLength(p, true);
                if (length &gt; 100)
                {
                    new_contours.Add(p);
                }
            }

            Cv2.DrawContours(dst, new_contours, -1, new Scalar(0, 0, 255), 2, LineTypes.AntiAlias, null, 1);

            BitmapSource bitmapSource = OpenCvSharp.WpfExtensions.BitmapSourceConverter.ToBitmapSource(dst); // Mat to BitmapSource
            ImageSource = ConvertBitmapSourceToBitmapImage(bitmapSource); //BitmapSource to BitmapImage

            Cv2.ImShow(&quot;dst&quot;, dst);
            Cv2.WaitKey(0); //시간 대기 함수의 값을 0으로 두어 키 입력이 있을때 까지 유지
        }</code></pre>
<p>UploadeImage()에 GetContour()를 추가한다.</p>
<pre><code class="language-cs">          private void UploadImage()
        {
            Microsoft.Win32.OpenFileDialog openFileDialog = new Microsoft.Win32.OpenFileDialog();
            openFileDialog.Filter = &quot;Image files (*.jpg, *.jpeg, *.png) | *.jpg; *.jpeg; *.png&quot;;

            if (openFileDialog.ShowDialog() == true)
            {
                string imagePath = openFileDialog.FileName;

                ImageSource = new BitmapImage(new Uri(imagePath));

                GetContour(imagePath);
            }
        }</code></pre>
<hr>
<h2 id="4-결과">4. 결과</h2>
<p>실행했을 때 MainWindow의 상태이다.
Mat 형식 데이터를 BitmapImage로 변환하여 Image에 바인딩했기 때문에 윤곽선이 검출된 상태의 이미지가 보인다.
new Scalar(0, 125, 125)에서 new Scalar(100, 255, 255)까지의 색 범위인 윤곽선을 찾아 new Scalar(0, 0, 255) 색으로 선을 그리도록 했기 때문에 첫 번째 줄의 노란색 사각형과 맨 아래 줄의 노란색 삼각형에 빨간 윤곽선이 그려진 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/20b66cc8-8dc9-49c6-8c4a-5cdd616bf269/image.png" alt="">
.ImShow로 띄운 window이다.
이미지의 원본사이즈 크기로 window가 생성되기 때문에 작게 보려면 resize가 필요할 것이다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/55c88d87-92ce-4790-9e9e-19b1be0e311d/image.png" alt=""></p>
<p>아래는 Console.WriteLine(&quot;new_contours count : &quot; + new_contours.Count); 를 추가 작성하여 콘솔로 new_contours의 count를 출력한건데 그려진 윤곽선의 개 수를 확인할 수 있다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/ba41eb2f-a8e0-4dc9-bcea-4a6f756bc523/image.png" alt=""></p>
<hr>
<h2 id="5-참조">5. 참조</h2>
<ul>
<li><a href="https://www.nuget.org/packages/OpenCvSharp4.runtime.win">https://www.nuget.org/packages/OpenCvSharp4.runtime.win</a></li>
<li><a href="https://www.nuget.org/packages/OpenCvSharp4.WpfExtensions">https://www.nuget.org/packages/OpenCvSharp4.WpfExtensions</a></li>
<li><a href="https://076923.github.io/posts/C-opencv4-16/">https://076923.github.io/posts/C-opencv4-16/</a></li>
<li><a href="https://redrainkim.github.io/opencvsharp/opencvsharp-study-10/">https://redrainkim.github.io/opencvsharp/opencvsharp-study-10/</a></li>
<li><a href="https://shalchoong.tistory.com/37">https://shalchoong.tistory.com/37</a></li>
</ul>
<hr>
<h2 id="6-보완할-점">6. 보완할 점</h2>
<blockquote>
<p>예제로 사용한 이미지는 깔끔하게 떨어지는 테두리를 갖고있기 때문에 윤곽선도 예쁘게 그려졌지만 복잡한 형상의 이미지의 윤곽선을 그리려고하면 지저분하게 보인다. 그럴땐 블러나 팽창, 침식 등의 메서드를 추가 적용하여 깔끔하게 그릴 수 있도록 처리하는 듯 하다.
윤곽선을 그리려고 OpenCVSharp 사용법을 알아본건 아니고 인식된 형태에 이벤트를 걸 수 있는 방법을 찾고있다. 되도록 MVVM 패턴을 준수하는 방식으로 찾아보려고 한다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C#] 동적으로 구성되는 Json 역직렬화 - Custom JsonConverter 작성하기]]></title>
            <link>https://velog.io/@yu_oolong/C-%EB%8F%99%EC%A0%81%EC%9C%BC%EB%A1%9C-%EA%B5%AC%EC%84%B1%EB%90%98%EB%8A%94-Json-%EC%97%AD%EC%A7%81%EB%A0%AC%ED%99%94-Custom-JsonConverter-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yu_oolong/C-%EB%8F%99%EC%A0%81%EC%9C%BC%EB%A1%9C-%EA%B5%AC%EC%84%B1%EB%90%98%EB%8A%94-Json-%EC%97%AD%EC%A7%81%EB%A0%AC%ED%99%94-Custom-JsonConverter-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 27 May 2024 02:56:50 GMT</pubDate>
            <description><![CDATA[<p>2024.06.03 수정했습니다.</p>
<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p><a href="https://velog.io/@yu_oolong/C">이 글</a>에서 동적으로 Key가 추가되는 구조의 Json Object를 역직렬화하는 방법에 대한 내용을 작성했는데 여기에 Custom JsonConverter를 작성하는 내용을 추가 작성한다.</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>VisualStudio 2022 / WPF 애플리케이션(.NET Framework 4.7.2)</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<p>다음과 같은 Json Object가 존재한다.</p>
<pre><code class="language-json">{
    &quot;FirstName&quot; : &quot;James&quot;,
    &quot;LastName&quot; : &quot;Newton-King&quot;,
    &quot;Temps&quot; : {
        &quot;Age&quot; : &quot;38&quot;,
        &quot;Adress_1&quot; : {
            &quot;PhoneNumber&quot; : &quot;000-0000-0000&quot;, 
            &quot;FAX&quot; : &quot;000-000-0000&quot;
        },
        &quot;Adress_2&quot; : {
            &quot;PhoneNumber&quot; : &quot;111-1111-1111&quot;, 
            &quot;FAX&quot; : &quot;111-111-1111&quot;
        }
    }
}</code></pre>
<p>이대로만 Class를 만들면 될 것 같지만 문제는 Temps 안의 Address_로 시작하는 Key는 Address_1 하나일 수도 있고 _2, _3...등 여러 개가 될 수도 있다.</p>
<pre><code class="language-json">        &quot;Adress_1&quot; : {
            &quot;PhoneNumber&quot; : &quot;000-0000-0000&quot;, 
            &quot;FAX&quot; : &quot;000-000-0000&quot;
        },
        &quot;Adress_2&quot; : {
            &quot;PhoneNumber&quot; : &quot;111-1111-1111&quot;, 
            &quot;FAX&quot; : &quot;111-111-1111&quot;
        },
        &quot;Adress_3&quot; : {
            &quot;PhoneNumber&quot; : &quot;222-2222-2222&quot;, 
            &quot;FAX&quot; : &quot;222-222-2222&quot;
        }
        .
        .
        .</code></pre>
<p>그럴때 class는 다음과 같이 작성하고</p>
<pre><code class="language-cs">        public class Employee
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public Temp Temps { get; set; }
        }

        public class Temp {
            public string Age { get; set; }
            public Dictionary&lt;string, Address&gt; Address { get; set; }
        }

        public class Address
        {
            public string PhoneNumber { get; set; }
            public string FAX { get; set; }
        }</code></pre>
<p>JsonConverter를 작성한다.
Json 문서의 이름 구조가 Address_ 일 것을 염두에 두고 작성한다.</p>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/2f7a7208-f808-4245-8e32-5a3477cf3fc2/image.png" alt=""></p>
<p>ReadJson에서 Json Object의 Property 중 이름이 Address_로 시작하는 것을 찾아서 Key와 Value를 Add한다.</p>
<pre><code class="language-cs">                foreach (var property in jObject.Properties())
                {
                    if (property.Name.StartsWith(&quot;Adress_&quot;))
                    {
                        var propertyName = property.Value.ToObject&lt;Address&gt;();
                        temp.Address.Add(property.Name, propertyName);
                    }
                }</code></pre>
<p>JsonConverter를 Dictionary Roles 위에 추가한다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/82306b63-12d0-4b56-ba99-fa22d74db9e5/image.png" alt=""></p>
<hr>
<h2 id="4-결과">4. 결과</h2>
<blockquote>
<p>Temps안의 Address Count = 2
<img src="https://velog.velcdn.com/images/yu_oolong/post/f741c166-8704-4f80-89ac-bb06f99f3892/image.png" alt=""></p>
</blockquote>
<blockquote>
<p>하나씩 살펴보면 Address_1, Address_2가 담긴 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/4a49458f-8003-4e51-b9ef-ebc3de8a67b3/image.png" alt="">
<img src="https://velog.velcdn.com/images/yu_oolong/post/7edbbdd9-f067-4301-aadd-cc93da45ddb4/image.png" alt=""></p>
</blockquote>
<hr>
<h2 id="5-참조">5. 참조</h2>
<ul>
<li><a href="https://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm">https://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm</a></li>
<li><a href="https://stackoverflow.com/questions/17721755/parsing-dynamic-json-string-into-string-in-c-sharp-using-json-net">https://stackoverflow.com/questions/17721755/parsing-dynamic-json-string-into-string-in-c-sharp-using-json-net</a></li>
<li><a href="https://github.com/dotliquid/dotliquid/issues/223">https://github.com/dotliquid/dotliquid/issues/223</a></li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C#] 동적으로 추가되는 Json key를 Class로 구현하기]]></title>
            <link>https://velog.io/@yu_oolong/C</link>
            <guid>https://velog.io/@yu_oolong/C</guid>
            <pubDate>Fri, 24 May 2024 00:21:14 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p>REST API로 URL을 요청하여 JSON을 반환 받으면 해당 JSON 구조대로 클래스를 만들어두고 역직렬화하여 값을 받게 되었다. 그런데 JSON 문서를 살펴보니 key의 이름이 동적으로 바뀌는 경우가 있었다. Newtonsoft.Json 라이브러리를 사용하는데 Dictionary를 사용하면 된다고 한다.</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>VisualStudio 2022 / WPF 애플리케이션(.NET Framework 4.7.2)</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<p>문서에 설명된 내용을 보면 api를 요청하여 받은 결과인 Json의 내용이 다음과 같다고 할때.</p>
<pre><code class="language-json">{
  &quot;key&quot; : &quot;value&quot;,
  &quot;key_value&quot; : {
      &quot;kv_1&quot; : &quot;abc&quot;,
      &quot;kv_2&quot; : &quot;def&quot;
  }
}</code></pre>
<p>위의 Json을 class로 표현한다면 아래와 같이 만들게 될 것이다.</p>
<pre><code class="language-cs">public class Temp 
{
  public string key { get; set; }
  public List&lt;KeyValue&gt; key_value { get; set; }
}

public class KeyValue 
{
  public string kv_1 { get; set; }
  public string kv_2 { get; set; }
}</code></pre>
<p>하지만 응답 결과에 따라 kv_3, kv_4 ... 이렇게 key가 늘어날 수 있다.
이럴땐 List가 아니라 Dictionary를 사용한다.</p>
<pre><code class="language-cs">public class Temp
{
  public string key { get; set; }
  public Dictionary&lt;string, string&gt; key_value { get; set; }
}</code></pre>
<p>string 형태의 json 내용을 역직렬화 하여 Temp 객체에 담을 수 있다.</p>
<pre><code class="language-cs">string str = &quot;{\&quot;key\&quot;:\&quot;value\&quot;,\&quot;key_value\&quot;:{\&quot;kv_1\&quot;:\&quot;abc\&quot;,\&quot;kv_2\&quot;:\&quot;def\&quot;}}&quot;;
Temp temp = JsonConvert.DeserializeObject&lt;Temp&gt;(str);</code></pre>
<hr>
<h2 id="4-결과">4. 결과</h2>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/edd29db0-a5cb-48df-951d-8af09c77fee7/image.png" alt="">
key_value가 Dictionary 형태로 담긴 것을 확인할 수 있다.</p>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C# WPF] Trigger에서 문자열 공백 Check하기 ]]></title>
            <link>https://velog.io/@yu_oolong/C-WPF-Trigger-Value%EC%97%90%EC%84%9C-Null-Check%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yu_oolong/C-WPF-Trigger-Value%EC%97%90%EC%84%9C-Null-Check%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 24 Jan 2024 05:55:56 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p>xaml에서 MultiDataTrigger의 Condition 작성 중 Null, 공백을 체크해야 할 일이 생겼다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/06298e7b-0073-4f8c-a55c-62ed7d495a70/image.png" alt="">
처음엔 Value 값으로 {x:Null}을 작성하면 되려나 했는데 작동하지 않았다.
Converter를 작성할까 하다가 더 찾아봤더니 네임스페이스를 추가하여 Value에서 공백을 체크할 수 있는 방법이 있었다.</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>VisualStudio 2022 / WPF 애플리케이션(.NET Framework 4.7.2)</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<p><strong>1. 네임스페이스 작성</strong>
<img src="https://velog.velcdn.com/images/yu_oolong/post/f98cce5a-13e2-4363-9a2d-19e00a637aa6/image.png" alt=""></p>
<pre><code class="language-xaml">    xmlns:sys=&quot;clr-namespace:System;assembly=mscorlib&quot;</code></pre>
<p><strong>2. xaml 작성</strong>
<img src="https://velog.velcdn.com/images/yu_oolong/post/74facda8-96ad-4b08-92a1-97abfdcd009c/image.png" alt=""></p>
<pre><code class="language-xaml">        &lt;StackPanel HorizontalAlignment=&quot;Center&quot; VerticalAlignment=&quot;Center&quot;&gt;
            &lt;TextBlock Text=&quot;값이 없으면 글씨 색이 빨강이 된다.&quot; /&gt;

            &lt;StackPanel Orientation=&quot;Horizontal&quot;&gt;
                &lt;Label Content=&quot; 값 : &quot; /&gt;
                &lt;TextBox x:Name=&quot;TextBox1&quot; Text=&quot;기본값&quot; VerticalContentAlignment=&quot;Center&quot; Width=&quot;100&quot;/&gt;
            &lt;/StackPanel&gt;
        &lt;/StackPanel&gt;</code></pre>
<p><strong>3. TextBlock에 Style DataTrigger 작성</strong></p>
<p>DataTrigger Binding은 ElementName에 TextBox의 x:Name을 작성하고 Value는 {x:Static sys:String.Empty}를 작성하여 문자열이 공백일 때를 체크한다.</p>
<pre><code class="language-xaml">        &lt;StackPanel HorizontalAlignment=&quot;Center&quot; VerticalAlignment=&quot;Center&quot;&gt;
            &lt;TextBlock Text=&quot;값이 없으면 글씨 색이 빨강이 된다.&quot;&gt;
                &lt;TextBlock.Style&gt;
                    &lt;Style TargetType=&quot;TextBlock&quot;&gt;
                        &lt;Style.Triggers&gt;
                            &lt;DataTrigger Binding=&quot;{Binding ElementName=TextBox1, Path=Text}&quot; Value=&quot;{x:Static sys:String.Empty}&quot;&gt;
                                &lt;Setter Property=&quot;Foreground&quot; Value=&quot;Red&quot;/&gt;
                            &lt;/DataTrigger&gt;
                        &lt;/Style.Triggers&gt;
                    &lt;/Style&gt;
                &lt;/TextBlock.Style&gt;
            &lt;/TextBlock&gt;

            &lt;StackPanel Orientation=&quot;Horizontal&quot;&gt;
                &lt;Label Content=&quot; 값 : &quot; /&gt;
                &lt;TextBox x:Name=&quot;TextBox1&quot; Text=&quot;기본값&quot; VerticalContentAlignment=&quot;Center&quot; Width=&quot;100&quot;/&gt;
            &lt;/StackPanel&gt;
        &lt;/StackPanel&gt;</code></pre>
<hr>
<h2 id="4-결과">4. 결과</h2>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/a2d4fc8c-f301-466b-a6cf-285294bae246/image.gif" alt=""></p>
<p>TextBox에 입력된 값이 없으면 TextBlock의 글씨 색이 변한다.
예제는 DataTrigger를 사용했는데 MultiDataTrigger의 Condition작성 시에도 사용 가능하다.</p>
<hr>
<h2 id="5-참조">5. 참조</h2>
<ul>
<li><a href="https://learn.microsoft.com/ko-kr/dotnet/desktop/xaml-services/types-for-primitives">https://learn.microsoft.com/ko-kr/dotnet/desktop/xaml-services/types-for-primitives</a></li>
<li><a href="https://stackoverflow.com/questions/19236137/datatrigger-on-empty-string">https://stackoverflow.com/questions/19236137/datatrigger-on-empty-string</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C# WPF] DatePicker Month-Year only]]></title>
            <link>https://velog.io/@yu_oolong/C-WPF-DatePicker-Month-Year-only</link>
            <guid>https://velog.io/@yu_oolong/C-WPF-DatePicker-Month-Year-only</guid>
            <pubDate>Fri, 05 Jan 2024 02:31:48 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p>기본 DatePicker는 <img src="https://velog.velcdn.com/images/yu_oolong/post/56155d1c-ca96-4127-b7ad-7e4429658f89/image.png" alt=""> 날짜까지 선택이 가능하다.
구체적인 날짜까지 선택하지 않고 년/월만 선택할 수 있는 DatePicker를 만들기 위해 열심히 구글 검색을 해본 결과 DatePikcer를 새로 정의해서 사용하는 방법을 StackOverflow에서 찾을 수 있었다.</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>VisualStudio 2022 / WPF 애플리케이션(.NET Framework 4.7.2)</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<ol>
<li>DatePickerCalendar.cs 추가
<img src="https://velog.velcdn.com/images/yu_oolong/post/a8048e45-8711-453f-848c-9c17d06b17c5/image.png" alt=""></li>
</ol>
<ol start="2">
<li><p>DataPickerCalendar.cs에 내용 작성</p>
<pre><code class="language-cs"> public class DatePickerCalendar
 {
     public static readonly DependencyProperty IsMonthYearProperty =
         DependencyProperty.RegisterAttached(&quot;IsMonthYear&quot;, typeof(bool), typeof(DatePickerCalendar),
                                             new PropertyMetadata(OnIsMonthYearChanged));

     public static bool GetIsMonthYear(DependencyObject dobj)
     {
         return (bool)dobj.GetValue(IsMonthYearProperty);
     }

     public static void SetIsMonthYear(DependencyObject dobj, bool value)
     {
         dobj.SetValue(IsMonthYearProperty, value);
     }

     private static void OnIsMonthYearChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs e)
     {
         var datePicker = (DatePicker)dobj;

         Application.Current.Dispatcher
             .BeginInvoke(DispatcherPriority.Loaded,
                          new Action&lt;DatePicker, DependencyPropertyChangedEventArgs&gt;(SetCalendarEventHandlers),
                          datePicker, e);
     }

     private static void SetCalendarEventHandlers(DatePicker datePicker, DependencyPropertyChangedEventArgs e)
     {
         if (e.NewValue == e.OldValue)
             return;

         if ((bool)e.NewValue)
         {
             datePicker.CalendarOpened += DatePickerOnCalendarOpened;
             datePicker.CalendarClosed += DatePickerOnCalendarClosed;
         }
         else
         {
             datePicker.CalendarOpened -= DatePickerOnCalendarOpened;
             datePicker.CalendarClosed -= DatePickerOnCalendarClosed;
         }
     }

     private static void DatePickerOnCalendarOpened(object sender, RoutedEventArgs routedEventArgs)
     {
         var calendar = GetDatePickerCalendar(sender);
         calendar.DisplayMode = CalendarMode.Year;

         calendar.DisplayModeChanged += CalendarOnDisplayModeChanged;
     }

     private static void DatePickerOnCalendarClosed(object sender, RoutedEventArgs routedEventArgs)
     {
         var datePicker = (DatePicker)sender;
         var calendar = GetDatePickerCalendar(sender);
         datePicker.SelectedDate = calendar.SelectedDate;

         calendar.DisplayModeChanged -= CalendarOnDisplayModeChanged;
     }

     private static void CalendarOnDisplayModeChanged(object sender, CalendarModeChangedEventArgs e)
     {
         var calendar = (System.Windows.Controls.Calendar)sender;
         if (calendar.DisplayMode != CalendarMode.Month)
             return;

         calendar.SelectedDate = GetSelectedCalendarDate(calendar.DisplayDate);

         var datePicker = GetCalendarsDatePicker(calendar);
         datePicker.IsDropDownOpen = false;
     }

     private static System.Windows.Controls.Calendar GetDatePickerCalendar(object sender)
     {
         var datePicker = (DatePicker)sender;
         var popup = (Popup)datePicker.Template.FindName(&quot;PART_Popup&quot;, datePicker);
         return ((System.Windows.Controls.Calendar)popup.Child);
     }

     private static DatePicker GetCalendarsDatePicker(FrameworkElement child)
     {
         var parent = (FrameworkElement)child.Parent;
         if (parent.Name == &quot;PART_Root&quot;)
             return (DatePicker)parent.TemplatedParent;
         return GetCalendarsDatePicker(parent);
     }

     private static DateTime? GetSelectedCalendarDate(DateTime? selectedDate)
     {
         if (!selectedDate.HasValue)
             return null;
         return new DateTime(selectedDate.Value.Year, selectedDate.Value.Month, 1);
     }
 }

 public class DatePickerDateFormat
 {
     public static readonly DependencyProperty DateFormatProperty =
         DependencyProperty.RegisterAttached(&quot;DateFormat&quot;, typeof(string), typeof(DatePickerDateFormat),
                                             new PropertyMetadata(OnDateFormatChanged));

     public static string GetDateFormat(DependencyObject dobj)
     {
         return (string)dobj.GetValue(DateFormatProperty);
     }

     public static void SetDateFormat(DependencyObject dobj, string value)
     {
         dobj.SetValue(DateFormatProperty, value);
     }

     private static void OnDateFormatChanged(DependencyObject dobj, DependencyPropertyChangedEventArgs e)
     {
         var datePicker = (DatePicker)dobj;

         Application.Current.Dispatcher.BeginInvoke(
             DispatcherPriority.Loaded, new Action&lt;DatePicker&gt;(ApplyDateFormat), datePicker);
     }
     private static void ApplyDateFormat(DatePicker datePicker)
     {
         var binding = new Binding(&quot;SelectedDate&quot;)
         {
             RelativeSource = new RelativeSource { AncestorType = typeof(DatePicker) },
             Converter = new DatePickerDateTimeConverter(),
             ConverterParameter = new Tuple&lt;DatePicker, string&gt;(datePicker, GetDateFormat(datePicker)),
             StringFormat = GetDateFormat(datePicker) // This is also new but didnt seem to help
         };

         var textBox = GetTemplateTextBox(datePicker);
         textBox.SetBinding(TextBox.TextProperty, binding);

         textBox.PreviewKeyDown -= TextBoxOnPreviewKeyDown;
         textBox.PreviewKeyDown += TextBoxOnPreviewKeyDown;

         var dropDownButton = GetTemplateButton(datePicker);

         datePicker.CalendarOpened -= DatePickerOnCalendarOpened;
         datePicker.CalendarOpened += DatePickerOnCalendarOpened;

         // Handle Dropdownbutton PreviewMouseUp to prevent issue of flickering textboxes
         dropDownButton.PreviewMouseUp -= DropDownButtonPreviewMouseUp;
         dropDownButton.PreviewMouseUp += DropDownButtonPreviewMouseUp;
     }

     private static ButtonBase GetTemplateButton(DatePicker datePicker)
     {
         return (ButtonBase)datePicker.Template.FindName(&quot;PART_Button&quot;, datePicker);
     }

     /// &lt;summary&gt;
     ///     Prevents a bug in the DatePicker, where clicking the Dropdown open button results in Text being set to default formatting regardless of StringFormat or binding overrides
     /// &lt;/summary&gt;
     private static void DropDownButtonPreviewMouseUp(object sender, MouseButtonEventArgs e)
     {
         var fe = sender as FrameworkElement;
         if (fe == null) return;

         var datePicker = fe.TryFindParent&lt;DatePicker&gt;();
         if (datePicker == null || datePicker.SelectedDate == null) return;

         var dropDownButton = GetTemplateButton(datePicker);

         // Dropdown button was clicked
         if (e.OriginalSource == dropDownButton &amp;&amp; datePicker.IsDropDownOpen == false)
         {
             // Open dropdown
             datePicker.SetCurrentValue(DatePicker.IsDropDownOpenProperty, true);

             // Mimic everything else in the standard DatePicker dropdown opening *except* setting textbox value 
             datePicker.SetCurrentValue(DatePicker.DisplayDateProperty, datePicker.SelectedDate.Value);

             // Important otherwise calendar does not work
             dropDownButton.ReleaseMouseCapture();

             // Prevent datePicker.cs from handling this event 
             e.Handled = true;
         }
     }

     private static TextBox GetTemplateTextBox(Control control)
     {
         control.ApplyTemplate();
         return (TextBox)control?.Template?.FindName(&quot;PART_TextBox&quot;, control);
     }

     private static void TextBoxOnPreviewKeyDown(object sender, KeyEventArgs e)
     {
         if (e.Key != Key.Return)
             return;

         /* DatePicker subscribes to its TextBox&#39;s KeyDown event to set its SelectedDate if Key.Return was
          * pressed. When this happens its text will be the result of its internal date parsing until it
          * loses focus or another date is selected. A workaround is to stop the KeyDown event bubbling up
          * and handling setting the DatePicker.SelectedDate. */

         e.Handled = true;

         var textBox = (TextBox)sender;
         var datePicker = (DatePicker)textBox.TemplatedParent;
         var dateStr = textBox.Text;
         var formatStr = GetDateFormat(datePicker);
         datePicker.SelectedDate = DatePickerDateTimeConverter.StringToDateTime(datePicker, formatStr, dateStr);
     }

     private static void DatePickerOnCalendarOpened(object sender, RoutedEventArgs e)
     {
         /* When DatePicker&#39;s TextBox is not focused and its Calendar is opened by clicking its calendar button
          * its text will be the result of its internal date parsing until its TextBox is focused and another
          * date is selected. A workaround is to set this string when it is opened. */

         var datePicker = (DatePicker)sender;
         var textBox = GetTemplateTextBox(datePicker);
         var formatStr = GetDateFormat(datePicker);
         textBox.Text = DatePickerDateTimeConverter.DateTimeToString(formatStr, datePicker.SelectedDate);
     }

     private class DatePickerDateTimeConverter : IValueConverter
     {
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {
             var formatStr = ((Tuple&lt;DatePicker, string&gt;)parameter).Item2;
             var selectedDate = (DateTime?)value;
             return DateTimeToString(formatStr, selectedDate);
         }

         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
         {
             var tupleParam = ((Tuple&lt;DatePicker, string&gt;)parameter);
             var dateStr = (string)value;
             return StringToDateTime(tupleParam.Item1, tupleParam.Item2, dateStr);
         }

         public static string DateTimeToString(string formatStr, DateTime? selectedDate)
         {
             return selectedDate.HasValue ? selectedDate.Value.ToString(formatStr) : null;
         }

         public static DateTime? StringToDateTime(DatePicker datePicker, string formatStr, string dateStr)
         {
             DateTime date;
             var canParse = DateTime.TryParseExact(dateStr, formatStr, CultureInfo.CurrentCulture,
                                                   DateTimeStyles.None, out date);

             if (!canParse)
                 canParse = DateTime.TryParse(dateStr, CultureInfo.CurrentCulture, DateTimeStyles.None, out date);

             return canParse ? date : datePicker.SelectedDate;
         }
     }
 }

 public static class FEExten
 {
     /// &lt;summary&gt;
     /// Finds a parent of a given item on the visual tree.
     /// &lt;/summary&gt;
     /// &lt;typeparam name=&quot;T&quot;&gt;The type of the queried item.&lt;/typeparam&gt;
     /// &lt;param name=&quot;child&quot;&gt;A direct or indirect child of the
     /// queried item.&lt;/param&gt;
     /// &lt;returns&gt;The first parent item that matches the submitted
     /// type parameter. If not matching item can be found, a null
     /// reference is being returned.&lt;/returns&gt;
     public static T TryFindParent&lt;T&gt;(this DependencyObject child)
         where T : DependencyObject
     {
         //get parent item
         DependencyObject parentObject = GetParentObject(child);

         //we&#39;ve reached the end of the tree
         if (parentObject == null) return null;

         //check if the parent matches the type we&#39;re looking for
         T parent = parentObject as T;
         if (parent != null)
         {
             return parent;
         }
         else
         {
             //use recursion to proceed with next level
             return TryFindParent&lt;T&gt;(parentObject);
         }
     }

     /// &lt;summary&gt;
     /// This method is an alternative to WPF&#39;s
     /// &lt;see cref=&quot;VisualTreeHelper.GetParent&quot;/&gt; method, which also
     /// supports content elements. Keep in mind that for content element,
     /// this method falls back to the logical tree of the element!
     /// &lt;/summary&gt;
     /// &lt;param name=&quot;child&quot;&gt;The item to be processed.&lt;/param&gt;
     /// &lt;returns&gt;The submitted item&#39;s parent, if available. Otherwise
     /// null.&lt;/returns&gt;
     public static DependencyObject GetParentObject(this DependencyObject child)
     {
         if (child == null) return null;

         //handle content elements separately
         ContentElement contentElement = child as ContentElement;
         if (contentElement != null)
         {
             DependencyObject parent = ContentOperations.GetParent(contentElement);
             if (parent != null) return parent;

             FrameworkContentElement fce = contentElement as FrameworkContentElement;
             return fce != null ? fce.Parent : null;
         }

         //also try searching for parent in framework elements (such as DockPanel, etc)
         FrameworkElement frameworkElement = child as FrameworkElement;
         if (frameworkElement != null)
         {
             DependencyObject parent = frameworkElement.Parent;
             if (parent != null) return parent;
         }

         //if it&#39;s not a ContentElement/FrameworkElement, rely on VisualTreeHelper
         return VisualTreeHelper.GetParent(child);
     }
 }</code></pre>
</li>
<li><p>xaml에서의 사용</p>
<pre><code class="language-xaml">&lt;DatePicker tk:DatePickerCalendar.IsMonthYear=&quot;True&quot; tk:DatePickerDateFormat.DateFormat=&quot;yyyy-MMM&quot; Text=&quot;yyyy-MMM&quot; SelectedDate=&quot;{Binding SEL_MONTH}&quot; Grid.Column=&quot;1&quot; Margin=&quot;0,0,5,0&quot; VerticalAlignment=&quot;Center&quot;/&gt;</code></pre>
<p>앞의 tk는 DatePickerCalendar.cs가 작성된 폴더이다.
namespace에 추가하면된다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/ca1e616f-cd4b-4618-ba13-0dd29e431dbb/image.png" alt=""></p>
</li>
</ol>
<p>선택한 년/월의 Binding은 CommandParameter를 사용한다...</p>
<hr>
<h2 id="4-결과">4. 결과</h2>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/1c859487-ecb1-4abd-9e77-85f76f654824/image.gif" alt=""></p>
<hr>
<h2 id="5-참조">5. 참조</h2>
<ul>
<li><a href="https://stackoverflow.com/questions/1798513/wpf-toolkit-datepicker-month-year-only">https://stackoverflow.com/questions/1798513/wpf-toolkit-datepicker-month-year-only</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C# WPF] Multi Binding, Multi Parameter - MVVM]]></title>
            <link>https://velog.io/@yu_oolong/C-WPF-Multi-Binding-Multi-Parameter-MVVM</link>
            <guid>https://velog.io/@yu_oolong/C-WPF-Multi-Binding-Multi-Parameter-MVVM</guid>
            <pubDate>Wed, 06 Dec 2023 07:39:52 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p>MVVM 패턴으로 작성된 프로젝트에서 예를 들어 하나의 버튼을 ViewModel에 눌렀을 때 CommandParameter를 전달하는 방법은??</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>VisualStudio 2022 / WPF 애플리케이션(.NET Framework 4.7.2)</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/8f594b34-0e1e-4ee0-a636-b330eea024c6/image.png" alt=""></p>
<ol>
<li>위와 같이 컨트롤을 배치했다고 합시다.
TextBox A와 TextBox B에 작성한 내용을 Click Button을 눌러
TextBox C와 TextBox D에 Binding 합니다.</li>
</ol>
<pre><code class="language-xaml">    &lt;Grid&gt;
        &lt;StackPanel HorizontalAlignment=&quot;Center&quot; VerticalAlignment=&quot;Center&quot;&gt;
            &lt;StackPanel Orientation=&quot;Horizontal&quot; HorizontalAlignment=&quot;Center&quot; VerticalAlignment=&quot;Center&quot; Margin=&quot;0,0,0,10&quot;&gt;
                &lt;Label Content=&quot;A :&quot; /&gt;
                &lt;TextBox x:Name=&quot;TextBoxA&quot; Width=&quot;100&quot; Text=&quot;{Binding TextBoxA}&quot; Margin=&quot;0,0,10,0&quot;/&gt;
                &lt;Label Content=&quot;B :&quot; /&gt;
                &lt;TextBox x:Name=&quot;TextBoxB&quot; Width=&quot;100&quot; Text=&quot;{Binding TextBoxB}&quot;/&gt;
            &lt;/StackPanel&gt;

            &lt;Button Content=&quot;Click&quot; Margin=&quot;0,0,0,10&quot; Cursor=&quot;Hand&quot; Command=&quot;{Binding bindingButtonCommand}&quot;&gt;
                &lt;Button.CommandParameter&gt;
                    &lt;MultiBinding Converter=&quot;{StaticResource MultiCommandParameterCv}&quot;&gt;
                        &lt;Binding ElementName=&quot;TextBoxA&quot; Path=&quot;Text&quot; /&gt;
                        &lt;Binding ElementName=&quot;TextBoxB&quot; Path=&quot;Text&quot; /&gt;
                    &lt;/MultiBinding&gt;
                &lt;/Button.CommandParameter&gt;
            &lt;/Button&gt;

            &lt;StackPanel Orientation=&quot;Horizontal&quot; HorizontalAlignment=&quot;Center&quot; VerticalAlignment=&quot;Center&quot;&gt;
                &lt;Label Content=&quot;C :&quot; /&gt;
                &lt;TextBox Width=&quot;100&quot; Text=&quot;{Binding TextBoxC}&quot; Margin=&quot;0,0,10,0&quot;/&gt;
                &lt;Label Content=&quot;D :&quot; /&gt;
                &lt;TextBox Width=&quot;100&quot; Text=&quot;{Binding TextBoxD}&quot;/&gt;
            &lt;/StackPanel&gt;
        &lt;/StackPanel&gt;
    &lt;/Grid&gt;</code></pre>
<ol start="2">
<li>Converter를 작성합니다.
Converter는 IMultiValueConverter를 상속 받습니다.</li>
</ol>
<pre><code class="language-cs">    public class MultiCommandParameterConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            return values.Clone();
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }</code></pre>
<ol start="4">
<li><p>Converter를 사용할 수 있도록 xaml의 namespace와 Resource를 추가합니다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/ad33735e-e671-41d3-833a-ddc7204d659d/image.png" alt="">
<img src="https://velog.velcdn.com/images/yu_oolong/post/f2bc6c09-1f39-4cc1-96bc-b9c15aa35736/image.png" alt=""></p>
</li>
<li><p>ViewModel에 Command와 Binding할 변수를 선언합니다.</p>
</li>
</ol>
<pre><code class="language-cs">
        public ICommand bindingButtonCommand { get; set; }

        private string _textBoxA;
        private string _textBoxB;
        private string _textBoxC;
        private string _textBoxD;

        public string TextBoxA
        {
            get { return _textBoxA; }
            set 
            {
                _textBoxA = value;
                OnPropertyChanged(nameof(TextBoxA));
            }
        }

        public string TextBoxB
        {
            get { return _textBoxB; }
            set
            {
                _textBoxB = value;
                OnPropertyChanged(nameof(TextBoxB));
            }
        }

        public string TextBoxC
        {
            get { return _textBoxC; }
            set
            {
                _textBoxC = value;
                OnPropertyChanged(nameof(TextBoxC));
            }
        }

        public string TextBoxD
        {
            get { return _textBoxD; }
            set
            {
                _textBoxD = value;
                OnPropertyChanged(nameof(TextBoxD));
            }
        }

        public Page5ViewModel() 
        {
            bindingButtonCommand = new RelayCommand&lt;object&gt;(bindingButton);
        }

        private void bindingButton(object parameters)
        {
            var parameter = (object[])parameters;

            TextBoxC = parameter[0] as string;
            TextBoxD = parameter[1] as string;
        }</code></pre>
<p>Button을 눌러 Command가 실행되면 CommandParameter를 통해 넘겨받은 값이 object형의 parameters에 들어있습니다.
배열로 Casting하여 var parameter에 담고
TextBoxC와 TextBoxD에 각각 string으로 Casting한 값을 담습니다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/42635b75-6f88-45e0-96cb-8e9d697ecd9b/image.png" alt=""></p>
<hr>
<h2 id="4-결과">4. 결과</h2>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/0b5a00a6-57a6-42a5-b35c-e16f2c60f1ad/image.gif" alt=""></p>
<hr>
<h2 id="5-참조">5. 참조</h2>
<ul>
<li><a href="https://gapal.tistory.com/28">https://gapal.tistory.com/28</a></li>
</ul>
<hr>
<h2 id="6-생각해볼점">6. 생각해볼점</h2>
<blockquote>
<p>사실 텍스트박스에 작성한 내용을 굳이 버튼을 클릭해서 Multi Parameter로 넘기지 않아도 다른 텍스트박스에 바인딩되게 할 수 있지만...
저는 주로 검색 기능 구현할 때 많이 쓰는 것 같아요.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C# WPF] 선택한 CheckBox 항목 여러 개를 Converter를 이용하여 가져오기 - MVVM]]></title>
            <link>https://velog.io/@yu_oolong/C-WPF-Converter%EB%A1%9C-%EC%84%A0%ED%83%9D%ED%95%9C-CheckBox-%ED%95%AD%EB%AA%A9-%EC%97%AC%EB%9F%AC-%EA%B0%9C-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0-MVVM</link>
            <guid>https://velog.io/@yu_oolong/C-WPF-Converter%EB%A1%9C-%EC%84%A0%ED%83%9D%ED%95%9C-CheckBox-%ED%95%AD%EB%AA%A9-%EC%97%AC%EB%9F%AC-%EA%B0%9C-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0-MVVM</guid>
            <pubDate>Tue, 05 Dec 2023 00:42:33 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p>이전(<a href="https://velog.io/@yu_oolong/C-WPF-GroupBox-%EB%82%B4%EC%9D%98-CheckBox-Content-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0">이 글</a>)에 싱글톤 패턴으로 페이지의 인스턴스를 호출하여 x:Name을 통해 CheckBox의 내용을 가져오는 방식을 사용했었다. 아무리 싱글톤으로 만든 인스턴스라지만 자주 호출하는 것이 영 꺼림칙하여 Xceed Wpf ToolKit을 사용하지 않고 Converter를 작성하여 CheckBox에서 선택한 항목을 List로 만드는 방법으로 변경해보았다. </p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>VisualStudio 2022 / WPF 애플리케이션(.NET Framework 4.7.2)</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<ol>
<li>ViewModel에는 화면에서 선택한 CheckBox를 받을 List가 필요하다.</li>
</ol>
<pre><code class="language-cs">        //CheckBox
        public List&lt;string&gt; CheckedList { get; set; }</code></pre>
<ol start="2">
<li>Converter를 작성한다</li>
</ol>
<pre><code class="language-cs">    public class CheckBoxToListConverter : IValueConverter
    {
        List&lt;string&gt; bound = new List&lt;string&gt;();

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (bound.Contains(parameter.ToString()))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            bool isChecked = (bool)value;

            if (isChecked)
            {
                bound.Add(parameter.ToString());
            }
            else
            {
                bound.Remove(parameter.ToString());
            }

            return bound;
        }
    }</code></pre>
<ol start="3">
<li>화면의 CheckBox에 List와 Converter를 바인딩한다.</li>
</ol>
<pre><code class="language-xaml">&lt;CheckBox Content=&quot;A&quot; IsChecked=&quot;{Binding CheckedList, Converter={StaticResource CheckBoxToListCV}, ConverterParameter=&#39;A&#39;}&quot;/&gt;
&lt;CheckBox Content=&quot;B&quot; IsChecked=&quot;{Binding CheckedList, Converter={StaticResource CheckBoxToListCV}, ConverterParameter=&#39;B&#39;}&quot;/&gt;
&lt;CheckBox Content=&quot;C&quot; IsChecked=&quot;{Binding CheckedList, Converter={StaticResource CheckBoxToListCV}, ConverterParameter=&#39;C&#39;}&quot;/&gt;
&lt;CheckBox Content=&quot;D&quot; IsChecked=&quot;{Binding CheckedList, Converter={StaticResource CheckBoxToListCV}, ConverterParameter=&#39;D&#39;}&quot;/&gt;</code></pre>
<ol start="4">
<li>CheckBox 선택한 항목을 콘솔에서 출력하여 확인하기</li>
</ol>
<pre><code class="language-cs">
for (int i = 0; i &lt; CheckedList.Count; i++) 
{
    Console.WriteLine(CheckedList[i]);
}
</code></pre>
<hr>
<h2 id="4-결과">4. 결과</h2>
<p>이전 코드를 활용하여 버튼을 누르면 콘솔에 출력되도록 작성해보았다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/dee4a18c-cd00-45e8-9617-f2934a5faf0b/image.gif" alt=""></p>
<hr>
<h2 id="5-참조">5. 참조</h2>
<ul>
<li><a href="https://stackoverflow.com/questions/60633809/binding-wpf-checkbox-ischecked-to-a-list">https://stackoverflow.com/questions/60633809/binding-wpf-checkbox-ischecked-to-a-list</a></li>
</ul>
<hr>
<h2 id="6-생각해볼점">6. 생각해볼점</h2>
<blockquote>
<p>Converter와 CommandParameter 등 이전보다 WPF에 대해 더 많이 알게 된 것 같은 기분이 드는 동시에...난 아직 스택오버플로우가 없으면 Converter 작성하기는 너무 어려운 것 같다. 😂</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] 공휴일 API]]></title>
            <link>https://velog.io/@yu_oolong/Spring-%EA%B3%B5%ED%9C%B4%EC%9D%BC-API</link>
            <guid>https://velog.io/@yu_oolong/Spring-%EA%B3%B5%ED%9C%B4%EC%9D%BC-API</guid>
            <pubDate>Sat, 18 Nov 2023 05:35:08 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p><a href="https://velog.io/@yu_oolong/Spring-JSTL%EB%A1%9C-%ED%99%94%EB%A9%B4%EC%97%90-D-day-%EB%B3%B4%EC%97%AC%EC%A3%BC%EA%B8%B0">https://velog.io/@yu_oolong/Spring-JSTL로-화면에-D-day-보여주기</a> 이 글에서 언급한 토이 프로젝트에서 캘린더를 구현하는데 캘린더에 공휴일을 표시해줄 것인지 말 것인지 토글로 on/off할 수 있도록 한다.</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>Java 1.8</li>
<li>SpringFramework</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<ol>
<li><p>공공  데이터 포털에서 한국천문연구원 특일 정보 오픈 API 활용 신청하기
<a href="https://www.data.go.kr/data/15012690/openapi.do">https://www.data.go.kr/data/15012690/openapi.do</a></p>
</li>
<li><p>Service - RequestAPI.java
<a href="https://minaminaworld.tistory.com/211">https://minaminaworld.tistory.com/211</a> 코드 사용</p>
</li>
</ol>
<pre><code class="language-java">public class RequestAPI {

    private static String secretKey = &quot;발급받은 서비스키&quot;;

    public static Map&lt;String, Object&gt; holidayInfoAPI(String year, String month) throws IOException {
        StringBuilder urlBuilder = new StringBuilder(&quot;http://apis.data.go.kr/B090041/openapi/service/SpcdeInfoService/getHoliDeInfo&quot;); /*URL*/
        urlBuilder.append(&quot;?&quot; + URLEncoder.encode(&quot;serviceKey&quot;, &quot;UTF-8&quot;) + &quot;=&quot; + secretKey); /*Service Key*/
        urlBuilder.append(&quot;&amp;&quot; + URLEncoder.encode(&quot;pageNo&quot;, &quot;UTF-8&quot;) + &quot;=&quot; + URLEncoder.encode(&quot;1&quot;, &quot;UTF-8&quot;)); /*페이지번호*/
        urlBuilder.append(&quot;&amp;&quot; + URLEncoder.encode(&quot;numOfRows&quot;, &quot;UTF-8&quot;) + &quot;=&quot; + URLEncoder.encode(&quot;10&quot;, &quot;UTF-8&quot;)); /*한 페이지 결과 수*/
        urlBuilder.append(&quot;&amp;&quot; + URLEncoder.encode(&quot;solYear&quot;, &quot;UTF-8&quot;) + &quot;=&quot; + URLEncoder.encode(year, &quot;UTF-8&quot;)); /*연 */
        urlBuilder.append(&quot;&amp;&quot; + URLEncoder.encode(&quot;solMonth&quot;, &quot;UTF-8&quot;) + &quot;=&quot; + URLEncoder.encode(month.length() == 1 ? &quot;0&quot; + month : month, &quot;UTF-8&quot;)); /*월*/
        urlBuilder.append(&quot;&amp;&quot; + URLEncoder.encode(&quot;_type&quot;, &quot;UTF-8&quot;) + &quot;=&quot; + URLEncoder.encode(&quot;json&quot;, &quot;UTF-8&quot;)); /* json으로 요청 */

        URL url = new URL(urlBuilder.toString());
        System.out.println(&quot;요청URL = &quot; + urlBuilder);

        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod(&quot;GET&quot;);
        conn.setRequestProperty(&quot;Content-type&quot;, &quot;application/json&quot;);
        System.out.println(&quot;Response code: &quot; + conn.getResponseCode());

        BufferedReader rd;
        if (conn.getResponseCode() &gt;= 200 &amp;&amp; conn.getResponseCode() &lt;= 300) {
            rd = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        } else {
            rd = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
        }
        StringBuilder sb = new StringBuilder();
        String line;
        while ((line = rd.readLine()) != null) {
            sb.append(line);
        }
        rd.close();
        conn.disconnect();
        // System.out.println(sb.toString());

        return string2Map(sb.toString());
    }

    /**
     * Json String을 Hashmap으로 반환
     *
     * @param json
     * @return
     */
    public static Map&lt;String, Object&gt; string2Map(String json) {
        ObjectMapper mapper = new ObjectMapper();
        Map&lt;String, Object&gt; map = null;

        try {
            map = mapper.readValue(json, Map.class);
            System.out.println(map);

        } catch (IOException e) {
            e.printStackTrace();
        }
        return map;
    }
}</code></pre>
<ol start="3">
<li><p>Controller
<a href="https://minaminaworld.tistory.com/211">https://minaminaworld.tistory.com/211</a> 코드 응용</p>
<pre><code class="language-java"> //공휴일 Ajax
 @PostMapping(&quot;/GetHoliday&quot;)
 @ResponseBody
 public ResponseEntity&lt;ArrayList&lt;HashMap&lt;String, Object&gt;&gt;&gt; holidayInfoApi(String year, String month) {

     System.out.println(&quot;year = &quot; + year);
     System.out.println(&quot;month = &quot; + month);

     ArrayList&lt;HashMap&lt;String, Object&gt;&gt; responseHolidayArr = new ArrayList&lt;HashMap&lt;String, Object&gt;&gt;();

     RequestAPI requestAPI = new RequestAPI();

     try {
         Map&lt;String, Object&gt; holidayMap = requestAPI.holidayInfoAPI(year, month);
         Map&lt;String, Object&gt; response = (Map&lt;String, Object&gt;) holidayMap.get(&quot;response&quot;);
         Map&lt;String, Object&gt; body = (Map&lt;String, Object&gt;) response.get(&quot;body&quot;);

         int totalCount = (int) body.get(&quot;totalCount&quot;);

         if(totalCount &lt;= 0) {
             System.out.println(&quot;공휴일 없음&quot;);
             System.out.println(&quot;body = &quot; + body);
         }
         if(totalCount == 1) {
             HashMap&lt;String, Object&gt; items = (HashMap&lt;String, Object&gt;) body.get(&quot;items&quot;);
             HashMap&lt;String, Object&gt; item = (HashMap&lt;String, Object&gt;) items.get(&quot;item&quot;);
             responseHolidayArr.add(item);
             System.out.println(&quot;item = &quot; + item);
         }
         if(totalCount &gt; 1) {
             HashMap&lt;String, Object&gt; items = (HashMap&lt;String, Object&gt;) body.get(&quot;items&quot;);
             ArrayList&lt;HashMap&lt;String, Object&gt;&gt; item = (ArrayList&lt;HashMap&lt;String,Object&gt;&gt;) items.get(&quot;item&quot;);
             for(HashMap&lt;String, Object&gt; itemMap : item) {
                 System.out.println(&quot;itemMap = &quot; + itemMap);
                 responseHolidayArr.add(itemMap);
             }
         }
     } catch (Exception e) {
         e.printStackTrace();
     }
     return new ResponseEntity&lt;&gt;(responseHolidayArr, HttpStatus.OK);
 }</code></pre>
</li>
<li><p>JSP</p>
<pre><code class="language-html">&lt;span&gt;공휴일 자동 표시&lt;/span&gt;
&lt;input data-toggle=&quot;toggle&quot; id=&quot;publicAutoN&quot; type=&quot;checkbox&quot;&gt;</code></pre>
</li>
<li><p>JavaScript</p>
<pre><code class="language-javascript"> $(document).ready(function() {
       //공휴일 자동 표시
     $(document).on(&quot;change&quot;, &quot;#publicAutoY&quot;, function(){
         console.log(&quot;Y-&gt;N&quot;);
         $(&quot;#publicAutoY&quot;).removeAttr(&quot;checked&quot;);
         $(&quot;#publicAutoY&quot;).attr(&quot;id&quot;, &quot;publicAutoN&quot;);

         var year = $(&quot;#year&quot;).text();
         var month = $(&quot;#month&quot;).text();

         //Ajax로 전송
         $.ajax({
             url : &#39;./GetHoliday&#39;,
             data : {
                 year : year,
                 month : month
             },
             type : &#39;POST&#39;,
             dataType : &#39;json&#39;,
             success : function(result) {
                 if(result != &quot;&quot;) {
                     //div id가 dateiDay인 것들을 전부 조회하여 dateiDay의 .text()값과 조회해온 공휴일 날짜를 비교하여 일치하면 dateContent에 .append()한다.
                     for(var i = 1; i &lt;= 31; i++) {
                         for(var j = 0; j &lt; result.length; j++) {
                             var dateName = result[j].dateName;
                             var dateId;
                             //ex)공휴일이 20231003이면 끝의 한 자리만 잘라내고 20231225면 끝의 두 자리면 잘라낸다.
                             if(((result[j].locdate).toString()).slice(-2).match(&quot;0&quot;)) {
                                 dateId = &quot;date&quot; + ((result[j].locdate).toString()).slice(-1);
                             }
                             else {
                                 dateId = &quot;date&quot; + ((result[j].locdate).toString()).slice(-2);
                             }
                             var dateContentId = dateId + &quot;Content&quot;;

                             var date = ((result[j].locdate).toString()).slice(-1);

                             if ($(&quot;#date&quot; + i + &quot;Day&quot;).text() == ((result[j].locdate).toString()).slice(-1) || $(&quot;#date&quot; + i + &quot;Day&quot;).text() == ((result[j].locdate).toString()).slice(-2)) {
                                 $(&quot;#&quot; + dateContentId).empty();
                             }
                         }
                     }
                 }
             }
         }); //End Ajax
     });

     $(document).on(&quot;change&quot;, &quot;#publicAutoN&quot;, function(){
         console.log(&quot;N-&gt;Y&quot;);
         $(&quot;#publicAutoN&quot;).attr(&quot;checked&quot;, &quot;checked&quot;);
         $(&quot;#publicAutoN&quot;).attr(&quot;id&quot;, &quot;publicAutoY&quot;);

         var year = $(&quot;#year&quot;).text();
         var month = $(&quot;#month&quot;).text();

         //Ajax로 전송
         $.ajax({
             url : &#39;./GetHoliday&#39;,
             data : {
                 year : year,
                 month : month
             },
             type : &#39;POST&#39;,
             dataType : &#39;json&#39;,
             success : function(result) {
                 if(result != &quot;&quot;) {
                     //div id가 dateiDay인 것들을 전부 조회하여 dateiDay의 .text()값과 조회해온 공휴일 날짜를 비교하여 일치하면 dateContent에 .append()한다.
                     for(var i = 1; i &lt;= 31; i++) {
                         for(var j = 0; j &lt; result.length; j++) {
                             var dateName = result[j].dateName;
                             var dateId;
                             //ex)공휴일이 20231003이면 끝의 한 자리만 잘라내고 20231225면 끝의 두 자리면 잘라낸다.
                             if(((result[j].locdate).toString()).slice(-2).match(&quot;0&quot;)) {
                                 dateId = &quot;date&quot; + ((result[j].locdate).toString()).slice(-1);
                             }
                             else {
                                 dateId = &quot;date&quot; + ((result[j].locdate).toString()).slice(-2);
                             }
                             var dateContentId = dateId + &quot;Content&quot;;

                             var date = ((result[j].locdate).toString()).slice(-1);

                             if ($(&quot;#date&quot; + i + &quot;Day&quot;).text() == ((result[j].locdate).toString()).slice(-1) || $(&quot;#date&quot; + i + &quot;Day&quot;).text() == ((result[j].locdate).toString()).slice(-2)) {
                                 $(&quot;#&quot; + dateId).css(&quot;color&quot;, &quot;#ff0000&quot;);
                                 $(&quot;#&quot; + dateContentId).append(&quot;&lt;br&gt;&quot;);
                                 $(&quot;#&quot; + dateContentId).append(&quot;&lt;a&gt;&lt;/a&gt;&amp;nbsp&quot;);
                                 $(&quot;#&quot; + dateContentId).append(dateName).css(&quot;color&quot;, &quot;#ff0000&quot;);
                             }
                         }
                     }
                 }
             }
         }); //End Ajax
     });
 }</code></pre>
</li>
</ol>
<hr>
<h2 id="4-결과">4. 결과</h2>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/7c4ff8bf-3d32-401c-aeb3-7c875290150b/image.gif" alt=""></p>
<hr>
<h2 id="5-참조">5. 참조</h2>
<ul>
<li><a href="https://www.data.go.kr/index.do">https://www.data.go.kr/index.do</a></li>
<li><a href="https://minaminaworld.tistory.com/211">https://minaminaworld.tistory.com/211</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C# WPF] Binding RelativeSource]]></title>
            <link>https://velog.io/@yu_oolong/C-WPF-Binding-RelativeSource</link>
            <guid>https://velog.io/@yu_oolong/C-WPF-Binding-RelativeSource</guid>
            <pubDate>Thu, 09 Nov 2023 01:04:12 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p>DataGrid의 ItemsSource에 컬렉션이 이미 바인딩 되어있을 때, 컬렉션에 포함된 내용 외에 DataGrid의 컬럼에 이벤트를 걸기 위한 Command를 ViewModel에서 찾아 호출하는 방법 찾기</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>VisualStudio 2022 / WPF 애플리케이션(.NET Framework 4.7.2)</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/d6f7e620-aa24-4f92-907f-7eb6acf77fa3/image.png" alt=""></p>
<ol>
<li>위와 같은 화면이 있다고 했을 때 관리자Y/N 컬럼에 CheckBox를 보이게 하고싶다. DataGridCheckBoxColumn을 사용하면 되지만 CheckBox가 Checked될 때 이벤트를 걸기 위해 DataGridTemplateColumn으로 CheckBox를 만들어주고 <a href="https://velog.io/@yu_oolong/C-WPF-Behavior-InvokeCommandAction">Behavior</a>
를 작성하여 Command를 Binding 하려고 하는데 목록에 만들어준 Command가 보이지 않는다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/561d7bd9-c01e-4c7b-bace-057e3261074d/image.png" alt=""></li>
</ol>
<p>이유는 아마 DataGrid의 ItemsSource로 ObservableCollection을 바인딩 해둬서 그런 것 같다. 바인딩한 컬렉션의 내용물 외에는 바인딩 목록에 나오지 않는 것이다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/3782f2a4-6797-4ad1-85d2-29af4fc901ed/image.png" alt=""></p>
<ol start="2">
<li>좀 길지만 ViewModel에서 작성해둔 Command를 Binding 하는 방법으로 RelativeSource 속성이 있다. RelativeSource는 바인딩 할 객체를 찾아준다. Page4ViewModel에서 찾겠다고 하고 Path를 작성하려고 하면 작성해둔 Command를 목록에서 찾을 수 있다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/26118af9-b1aa-4053-8d40-ece80afdb239/image.png" alt=""></li>
</ol>
<pre><code class="language-xaml">            &lt;DataGrid x:Name=&quot;ExcelData&quot; ItemsSource=&quot;{Binding excelContentsList}&quot; CanUserAddRows=&quot;False&quot;&gt;
                &lt;DataGrid.Columns&gt;
                    &lt;DataGridTextColumn Header=&quot;이름&quot; Binding=&quot;{Binding NAME}&quot;/&gt;
                    &lt;DataGridTextColumn Header=&quot;품목&quot;  Binding=&quot;{Binding MANUFACTURER}&quot;/&gt;
                    &lt;DataGridTextColumn Header=&quot;제조사&quot; Binding=&quot;{Binding NUMBER}&quot;/&gt;
                    &lt;DataGridTextColumn Header=&quot;날짜&quot; Binding=&quot;{Binding INCOMING_DATE}&quot;/&gt;
                    &lt;DataGridTemplateColumn Header=&quot;관리자Y/N&quot;&gt;
                        &lt;DataGridTemplateColumn.CellTemplate&gt;
                            &lt;DataTemplate&gt;
                                &lt;CheckBox&gt;
                                    &lt;i:Interaction.Triggers&gt;
                                        &lt;i:EventTrigger EventName=&quot;Checked&quot;&gt;
                                            &lt;i:InvokeCommandAction Command=&quot;{Binding }&quot;/&gt;
                                        &lt;/i:EventTrigger&gt;
                                    &lt;/i:Interaction.Triggers&gt;
                                &lt;/CheckBox&gt;
                            &lt;/DataTemplate&gt;
                        &lt;/DataGridTemplateColumn.CellTemplate&gt;
                    &lt;/DataGridTemplateColumn&gt;
                &lt;/DataGrid.Columns&gt;
            &lt;/DataGrid&gt;</code></pre>
<hr>
<h2 id="4-참조">4. 참조</h2>
<ul>
<li><a href="https://stackoverflow.com/questions/10931856/mvvm-wpf-bindings-with-relativesource">https://stackoverflow.com/questions/10931856/mvvm-wpf-bindings-with-relativesource</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C# WPF] DataGrid Style 변경하기]]></title>
            <link>https://velog.io/@yu_oolong/C-WPF-DataGrid-Style</link>
            <guid>https://velog.io/@yu_oolong/C-WPF-DataGrid-Style</guid>
            <pubDate>Wed, 25 Oct 2023 05:34:07 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p>WPF DataGrid의 선 색, 굵기와 행을 선택했을 때 글자 색, 배경 색 변경 방법에 대하여...</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>VisualStudio 2022 / WPF 애플리케이션(.NET Framework 4.7.2)</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<ol>
<li><p>화면에 배치한 DataGrid가 Grid 안에 있는 DataGrid여야 한다.</p>
</li>
<li><p>DataGrid Style을 지정하는 태그들은 &lt;Grid.Resources&gt; 안에 작성한다.</p>
</li>
<li><p>가로 선은 Red, 세로 선은 Blue로 지정한다.</p>
</li>
<li><p>Trigger로 행이 선택됐을 때(IsSelected가 True일때), 선택한 행의 Background 색은 Beige, Foreground 색은 Brown이 되도록 한다.</p>
<pre><code class="language-xaml"> &lt;Grid Grid.Row=&quot;2&quot; Margin=&quot;10,0,10,0&quot;&gt;
         &lt;Grid.Resources&gt;
             &lt;Style TargetType=&quot;DataGrid&quot;&gt;
                 &lt;Setter Property=&quot;HorizontalGridLinesBrush&quot; Value=&quot;red&quot;/&gt;
                 &lt;Setter Property=&quot;VerticalGridLinesBrush&quot; Value=&quot;Blue&quot; /&gt;
             &lt;/Style&gt;
             &lt;Style TargetType=&quot;DataGridColumnHeader&quot;&gt;
                 &lt;Setter Property=&quot;HorizontalContentAlignment&quot; Value=&quot;Center&quot;/&gt;
             &lt;/Style&gt;
             &lt;Style TargetType=&quot;DataGridCell&quot;&gt;
                 &lt;Style.Triggers&gt;
                     &lt;Trigger Property=&quot;IsSelected&quot; Value=&quot;True&quot;&gt;
                         &lt;Setter Property=&quot;Background&quot; Value=&quot;Beige&quot;/&gt;
                         &lt;Setter Property=&quot;Foreground&quot; Value=&quot;Brown&quot;/&gt;
                     &lt;/Trigger&gt;
                 &lt;/Style.Triggers&gt;
             &lt;/Style&gt;
         &lt;/Grid.Resources&gt;

         &lt;DataGrid x:Name=&quot;ExcelData&quot; ItemsSource=&quot;{Binding 
             &lt;DataGrid.Columns&gt;
                 &lt;DataGridTextColumn Header=&quot;이름&quot; Binding=&quot;{Binding NAME}&quot;/&gt;
                 &lt;DataGridTextColumn Header=&quot;품목&quot;  Binding=&quot;{Binding MANUFACTURER}&quot;/&gt;
                 &lt;DataGridTextColumn Header=&quot;제조사&quot; Binding=&quot;{Binding NUMBER}&quot;/&gt;
                 &lt;DataGridTextColumn Header=&quot;날짜&quot; Binding=&quot;{Binding INCOMING_DATE}&quot;/&gt;
                 &lt;DataGridTextColumn Header=&quot;관리자&quot; Binding=&quot;{Binding MANAGER}&quot;/&gt;
             &lt;/DataGrid.Columns&gt;
         &lt;/DataGrid&gt;
     &lt;/Grid&gt;</code></pre>
<p>DataGridColumnHeader의 경우 가운데 정렬만 Style로 지정해놓았는데 다른 속성들과 마찬가지로 배경 색, 글자 색, 글자 크기 등의 Style 변경이 가능하다.</p>
</li>
</ol>
<hr>
<h2 id="4-결과">4. 결과</h2>
<blockquote>
<p>셀의 가로 선은 빨강, 세로 선은 파랑이고 
선택한 행의 셀 배경 색이 베이지, 글자 색은 브라운이 되는 트리거가 작동된 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/368d5751-7dfa-4ae0-88e9-1ab5f74eccdf/image.gif" alt="">
이건 Style이 적용되지 않은 기본 DataGrid
<img src="https://velog.velcdn.com/images/yu_oolong/post/6e2f32a3-882e-4e67-a326-b05995b2466b/image.png" alt=""></p>
</blockquote>
<hr>
<h2 id="5-참조">5. 참조</h2>
<ul>
<li>마이크로소프트 공식 문서</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C# WPF] GroupBox 내의 CheckBox Content 가져오기]]></title>
            <link>https://velog.io/@yu_oolong/C-WPF-GroupBox-%EB%82%B4%EC%9D%98-CheckBox-Content-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0</link>
            <guid>https://velog.io/@yu_oolong/C-WPF-GroupBox-%EB%82%B4%EC%9D%98-CheckBox-Content-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0</guid>
            <pubDate>Fri, 20 Oct 2023 09:56:12 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p>Check된 CheckBox의 Content를 저장하여 TextBlock에 보여준다.</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>VisualStudio 2022 / WPF 애플리케이션(.NET Framework 4.7.2)</li>
<li>이 <a href="https://velog.io/@yu_oolong/C-WPF-Xceed-WPF-Toolkit-%EC%84%A4%EC%B9%98">링크</a>에 해당하는 ToolKit 설치를 전제로 한다.</li>
<li><a href="https://velog.io/@yu_oolong/C-WPF-Button%EC%9D%84-%EB%88%8C%EB%9F%AC-Frame%EC%97%90-Page-%EB%9D%84%EC%9A%B0%EA%B8%B0-1">https://velog.io/@yu_oolong/C-WPF-Button을-눌러-Frame에-Page-띄우기-1</a> 에서 작성한 프로젝트에서 추가한 내용이다.</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<ol>
<li>GroupBox 내에 Grid가 있고 그 Grid안에 CheckBox를 세 개 배치했다. 
그리고 아래에 Button과 보이진 않지만 TextBlock이 있다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/45cc47a1-5856-484b-942b-a4c4f05331b1/image.png" alt=""></li>
</ol>
<p>xaml 코드)</p>
<pre><code class="language-xaml">    &lt;Grid&gt;
        &lt;Grid HorizontalAlignment=&quot;Center&quot; VerticalAlignment=&quot;Center&quot; &gt;
            &lt;Grid.RowDefinitions&gt;
                &lt;RowDefinition Height=&quot;Auto&quot; /&gt;
                &lt;RowDefinition Height=&quot;Auto&quot; /&gt;
                &lt;RowDefinition Height=&quot;Auto&quot; /&gt;
            &lt;/Grid.RowDefinitions&gt;

            &lt;GroupBox Grid.Row=&quot;0&quot; x:Name=&quot;CheckBoxGroup&quot; Header=&quot;그룹 박스&quot;&gt;
                &lt;Grid HorizontalAlignment=&quot;Center&quot;&gt;
                    &lt;Grid.RowDefinitions&gt;
                        &lt;RowDefinition /&gt;
                        &lt;RowDefinition /&gt;
                        &lt;RowDefinition /&gt;
                    &lt;/Grid.RowDefinitions&gt;

                    &lt;CheckBox x:Name=&quot;choose1&quot; Grid.Row=&quot;0&quot; Content=&quot;선택1&quot;/&gt;
                    &lt;CheckBox x:Name=&quot;choose2&quot; Grid.Row=&quot;1&quot; Content=&quot;선택2&quot;/&gt;
                    &lt;CheckBox x:Name=&quot;choose3&quot; Grid.Row=&quot;2&quot; Content=&quot;선택3&quot;/&gt;
                &lt;/Grid&gt;
            &lt;/GroupBox&gt;
            &lt;Button x:Name=&quot;chooseBtn&quot; Grid.Row=&quot;1&quot; Content=&quot;선택&quot;/&gt;
            &lt;TextBlock x:Name=&quot;Content&quot; Grid.Row=&quot;2&quot; Width=&quot;200&quot;/&gt;
        &lt;/Grid&gt;

    &lt;/Grid&gt;</code></pre>
<p>GroupBox와 CheckBox 컨트롤, 선택한 CheckBox의 Content를 보여줄 TextBlock의 x:Name을 지정해주어야 한다.
ViewModel에서 지정한 x:Name을 통해 Content를 가져올 것이기 때문이다.</p>
<ol start="2">
<li><p>View의 코드 비하인드에서 View를 싱글톤으로 만든다.</p>
<pre><code class="language-cs"> public partial class Page6 : Page
 {
     private static Page6 _Instance = null;

     public static Page6 GetInstance()
     {
         if (_Instance == null || !_Instance.IsLoaded)
         {
             _Instance = new Page6();
         }

         return _Instance;
     }

     public Page6()
     {
         InitializeComponent();

         _Instance = this;
     }
 }</code></pre>
</li>
<li><p>ViewModel에서 선택 버튼을 누르면 TextBlock에 내용을 보여주는 Command를 작성한다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/d0711698-93d7-447f-9432-32630e30c4f0/image.png" alt=""></p>
</li>
<li><p>chooseCheckBox에 로직을 작성하기 위해 TextBlock에 바인딩 할 데이터를 선언한다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/a3ff53d4-1ca7-48a5-a97e-1effc5f85e17/image.png" alt=""></p>
</li>
<li><p>싱글톤으로 만든 View의 인스턴스를 호출하고 x:Name으로 지정한 컨트롤에 접근하여 내용을 가져오는데,
.FindLogicalChildren&lt;&gt;() 메소드는 Xceed WPF Toolkit을 설치해야 사용할 수 있다.
CheckBoxGroup이라는 x:Name을 가진 GroupBox 트리 내의 CheckBox 컨트롤들을 CheckBox 타입의 checkBox에 담고 if문에서 IsChecked가 True인 CheckBox의 Content를 string 변수 cb에 담은 후 cbStr에 이어붙인다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/7617f220-6c77-4642-9cba-373b205b3146/image.png" alt=""></p>
</li>
</ol>
<p>ViewModel 코드)</p>
<pre><code class="language-cs">    internal class Page6ViewModel : BaseViewModel
    {
        public ICommand chooseCheckBoxCommand { get; set; }

        public List&lt;string&gt; checkBoxList = new List&lt;string&gt;();

        private string _checkBoxText { get; set; }

        public string CheckBoxText
        {
            get { return _checkBoxText; }
            set 
            { 
                _checkBoxText = value; 
                OnPropertyChanged(nameof(CheckBoxText)); 
            }
        }

        public Page6ViewModel() 
        {
            chooseCheckBoxCommand = new RelayCommand(chooseCheckBox);
        }

        private void chooseCheckBox()
        {
            Page6 page6 = Page6.GetInstance();

            string cb = &quot;&quot;;
            string cbStr = &quot;&quot;;

            foreach(CheckBox checkBox in page6.CheckBoxGroup.FindLogicalChildren&lt;CheckBox&gt;())
            {
                if(checkBox.IsChecked == true)
                {
                    cb = checkBox.Content.ToString();
                    cbStr += cb;
                }
            }

            CheckBoxText = cbStr;
        }

    }</code></pre>
<hr>
<h2 id="4-결과">4. 결과</h2>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/9e5a2617-c2a2-4fba-89d7-cad5e48ed272/image.gif" alt=""></p>
<blockquote>
<p>문자열을 이어붙여 하나의 string 변수에 담는게 아니라 나눠서 저장하고 싶다면 List를 사용하면 된다.</p>
</blockquote>
<hr>
<h2 id="5-생각해보기">5. 생각해보기</h2>
<p>사실 다른 프로젝트를 하면서 먼저 Xceed Wpf ToolKit을 설치해 뒀었기 때문에 아무 생각없이 FindLogicalChildren...메소드를 사용했었다. 그러다 기록을 위해 연습용 솔루션에서 똑같이 작성하려고 하니 자동완성으로 저 메소드가 생기질 않아 난감해했었다. 찾아보니 그제야 Xceed Wpf ToolKit에서 제공하는 메소드였던 것임을 알게 되었다...
ToolKit을 설치하지 않고도 선택한 CheckBox의 Content의 값을 가져오는 방법이 분명 있을텐데 왜 못찾았을까?? 구글링도...스스로 생각해보는 힘도 둘 다 모자란 것 같다...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C# WPF] 외부 파일 실행하기]]></title>
            <link>https://velog.io/@yu_oolong/C-WPF-%EC%99%B8%EB%B6%80-%ED%8C%8C%EC%9D%BC-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@yu_oolong/C-WPF-%EC%99%B8%EB%B6%80-%ED%8C%8C%EC%9D%BC-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 10 Oct 2023 01:35:48 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p>버튼을 눌러 .txt나 .xlsx같은 확장자를 가진 파일을 연다.</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>VisualStudio 2022 / WPF 애플리케이션(.NET Framework 4.7.2)</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<h3 id="3-1-프로젝트-생성-및-폴더-구조">3-1. 프로젝트 생성 및 폴더 구조</h3>
<p><a href="https://velog.io/@yu_oolong/C-WPF-MVVM-%ED%8C%A8%ED%84%B4%EC%9C%BC%EB%A1%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0">https://velog.io/@yu_oolong/C-WPF-MVVM-패턴으로-프로젝트-시작하기</a> 와 같이 프로젝트를 생성한다.
폴더 구조는 다음과 같다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/db4503e7-7f5b-4cc7-8ec4-4682e2337ef7/image.png" alt=""></p>
<h3 id="3-2-컨트롤-배치">3-2. 컨트롤 배치</h3>
<p>버튼을 눌러서 외부 파일을 실행하기 위해 화면에 Button 컨트롤을 배치한다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/edd8e536-4e80-4231-aa03-b9c513db1c47/image.png" alt=""></p>
<pre><code class="language-xaml">&lt;Page x:Class=&quot;PracticeProject2.Views.Page5&quot;
      xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
      xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
      xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot; 
      xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot;
      xmlns:local=&quot;clr-namespace:PracticeProject2.Views&quot;
      mc:Ignorable=&quot;d&quot; 
      d:DesignHeight=&quot;450&quot; d:DesignWidth=&quot;800&quot;
      Title=&quot;Page5&quot;
      Background=&quot;White&quot;&gt;

    &lt;Grid&gt;
        &lt;StackPanel HorizontalAlignment=&quot;Center&quot; VerticalAlignment=&quot;Center&quot;&gt;
            &lt;Button Content=&quot;Open File&quot; Width=&quot;100&quot; Height=&quot;50&quot; Cursor=&quot;Hand&quot;/&gt;
        &lt;/StackPanel&gt;
    &lt;/Grid&gt;
&lt;/Page&gt;
</code></pre>
<h3 id="3-3-relaycommand-baseviewmodel-작성">3-3. RelayCommand, BaseViewModel 작성</h3>
<ol>
<li>Commands 폴더에 RelayCommand.cs 생성하여 작성한다.<pre><code class="language-cs">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
</code></pre>
</li>
</ol>
<p>namespace PackagingSystem.Commands
{
    public class RelayCommand : ICommand
    {
        private readonly Action _execute;
        private readonly Func<bool> _canExecute;</p>
<pre><code>    public RelayCommand(Action execute, Func&lt;bool&gt; canExecute = null)
    {
        _execute = execute ?? throw new ArgumentNullException(nameof(execute));
        _canExecute = canExecute;
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public bool CanExecute(object parameter) =&gt; _canExecute == null || _canExecute();

    public void Execute(object parameter) =&gt; _execute();
}</code></pre><p>}</p>
<pre><code>
2. ViewModels 폴더에 INotifyPropertyChanged를 상속받는 BaseViewModel.cs을 생성하여 작성한다.
```cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace PracticeProject2.ViewModels
{
    internal class BaseViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        protected bool SetProperty&lt;T&gt;(ref T backingField, T value, [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer&lt;T&gt;.Default.Equals(backingField, value)) return false;
            backingField = value;
            OnPropertyChanged(propertyName);
            return true;
        }
    }
}</code></pre><h3 id="3-4-baseviewmodel을-상속-받는-viewmodel-작성">3-4. BaseViewModel을 상속 받는 ViewModel 작성</h3>
<ol>
<li>View인 page5.xaml의 ViewModel인 Page5ViewModel.cs를 생성한다.</li>
<li>BaseViewModel을 상속 받고 Command를 작성한다.</li>
<li>Process.Start 메서드로 경로에 해당하는 파일을 실행하는 Command이다.<pre><code class="language-cs">using PracticeProject2.Commands;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
</code></pre>
</li>
</ol>
<p>namespace PracticeProject2.ViewModels
{
    internal class Page5ViewModel : BaseViewModel
    {
        public ICommand openFileCommand { get; set; }</p>
<pre><code>    public Page5ViewModel() 
    {
        openFileCommand = new RelayCommand(openFile);
    }

    private void openFile()
    {
        string directory = @&quot;D:\\Edu\\&quot;;
        string filename = &quot;memo.txt&quot;;
        string filepath = directory + filename;

        Process.Start(filepath);
    }

}</code></pre><p>}</p>
<pre><code>
### 3-5. View의 Button에 Command 바인딩하기
1. ViewModels 네임스페이스 명시
2. DataContext 연결하기
3. Button 컨트롤에 Command Binding하기

```xaml
&lt;Page x:Class=&quot;PracticeProject2.Views.Page5&quot;
      xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
      xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
      xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot; 
      xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot; 
      xmlns:vm=&quot;clr-namespace:PracticeProject2.ViewModels&quot;
      xmlns:local=&quot;clr-namespace:PracticeProject2.Views&quot;
      mc:Ignorable=&quot;d&quot; 
      d:DesignHeight=&quot;450&quot; d:DesignWidth=&quot;800&quot;
      Title=&quot;Page5&quot;
      Background=&quot;White&quot;&gt;

    &lt;Page.DataContext&gt;
        &lt;vm:Page5ViewModel /&gt;
    &lt;/Page.DataContext&gt;

    &lt;Grid&gt;
        &lt;StackPanel HorizontalAlignment=&quot;Center&quot; VerticalAlignment=&quot;Center&quot;&gt;
            &lt;Button Content=&quot;Open File&quot; Width=&quot;100&quot; Height=&quot;50&quot; Cursor=&quot;Hand&quot; Command=&quot;{Binding openFileCommand}&quot;/&gt;
        &lt;/StackPanel&gt;
    &lt;/Grid&gt;
&lt;/Page&gt;
</code></pre><hr>
<h2 id="4-결과">4. 결과</h2>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/643a38e1-0029-4aa8-a7c3-a36887a392c3/image.png" alt="">
<img src="https://velog.velcdn.com/images/yu_oolong/post/24e1d5a0-3466-4958-881b-9c0c68dab155/image.png" alt="">
실행 후 버튼을 클릭하면 경로로 지정한 파일인 memo.txt 메모장 파일이 실행됐다.
.xlsx 파일을 경로로 지정하면 엑셀이 실행된다.</p>
<hr>
<h2 id="5-참조">5. 참조</h2>
<p>Process.Start 메서드</p>
<ul>
<li><a href="https://learn.microsoft.com/ko-kr/dotnet/api/system.diagnostics.process.start?view=net-7.0">https://learn.microsoft.com/ko-kr/dotnet/api/system.diagnostics.process.start?view=net-7.0</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C# WPF] Excel Import to Datagrid]]></title>
            <link>https://velog.io/@yu_oolong/C-WPF-Excel-Import-to-Datagrid</link>
            <guid>https://velog.io/@yu_oolong/C-WPF-Excel-Import-to-Datagrid</guid>
            <pubDate>Thu, 21 Sep 2023 04:47:18 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p>파일 탐색기에서 Excel 파일을 불러와 그 내용을 DataGrid에 보여주는 방법...
사실 구글링했을 때 방법이 안나오는건 아닌데 거의 코드 비하인드에 한 방식이기 때문에 MVVM 패턴으로 작성해봤다...근데 코드 비하인드에서의 작성이 아예 없지는 않아서 MVVM 패턴에는 위배되는 것 같다.😂</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>VisualStudio 2022 / WPF 애플리케이션(.NET Framework 4.7.2)</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<h3 id="3-1-할-일-정하기">3-1. 할 일 정하기</h3>
<p><a href="https://velog.io/@yu_oolong/C-WPF-Behavior-InvokeCommandAction">https://velog.io/@yu_oolong/C-WPF-Behavior-InvokeCommandAction</a> 와 같은 솔루션에서 진행한다.
id, pw를 manager로 입력하면 Menu4<del>6까지 보여주고 각 메뉴들은 Page4</del>6을 보여준다. 
그 중 Page4에서 다음의 내용을 수행한다.</p>
<ul>
<li>1번에서 파일찾기 버튼을 눌러 파일탐색기에서 파일의 이름을 찾고</li>
<li>2번에서 찾은 파일을 열어 DataGrid에 보여준다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/e60cab98-2294-4ce2-bf06-1b92f48fb856/image.png" alt=""></p>
<h3 id="3-2-xaml-컨트롤-배치하기">3-2. xaml 컨트롤 배치하기</h3>
<p>참고로 UI는 <a href="https://nomadcoder.tistory.com/entry/WPF-Excel-%ED%8C%8C%EC%9D%BC-%EC%9D%BD%EC%96%B4%EC%84%9C-Datagrid-%EC%97%90-%EB%84%A3%EA%B8%B0-Excel-Import">이 분</a> 화면을 보고 비슷하게 만들었다.</p>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/812851b3-cee1-4bba-ab56-d1cf7af995d7/image.png" alt=""></p>
<pre><code class="language-xaml">&lt;Page x:Class=&quot;PracticeProject2.Views.Page4&quot;
      xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
      xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
      xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot; 
      xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot; 
      xmlns:local=&quot;clr-namespace:PracticeProject2.Views&quot;
      mc:Ignorable=&quot;d&quot; 
      d:DesignHeight=&quot;450&quot; d:DesignWidth=&quot;800&quot;
      Title=&quot;Page4&quot;
      Background=&quot;White&quot;&gt;

    &lt;Grid&gt;
        &lt;Grid.RowDefinitions&gt;
            &lt;RowDefinition Height=&quot;100*&quot;/&gt;
            &lt;RowDefinition Height=&quot;270*&quot;/&gt;
            &lt;RowDefinition Height=&quot;80*&quot;/&gt;
        &lt;/Grid.RowDefinitions&gt;

        &lt;Grid Grid.Row=&quot;0&quot; Margin=&quot;10,0,10,0&quot;&gt;
            &lt;StackPanel Orientation=&quot;Horizontal&quot; HorizontalAlignment=&quot;Center&quot; VerticalAlignment=&quot;Center&quot;&gt;
                &lt;Label Content=&quot;파일 이름&quot; /&gt;
                &lt;TextBox x:Name=&quot;fileUrlTextBox&quot; Width=&quot;450&quot; Margin=&quot;10,0,10,0&quot; /&gt;
                &lt;Button Content=&quot;파일찾기&quot;/&gt;
            &lt;/StackPanel&gt;
        &lt;/Grid&gt;

        &lt;Grid Grid.Row=&quot;1&quot; Margin=&quot;10,0,10,0&quot;&gt;
            &lt;DataGrid x:Name=&quot;ExcelData&quot;&gt;
                &lt;DataGrid.Resources&gt;
                    &lt;Style TargetType=&quot;DataGridColumnHeader&quot;&gt;
                        &lt;Setter Property=&quot;HorizontalContentAlignment&quot; Value=&quot;Center&quot;/&gt;
                    &lt;/Style&gt;
                &lt;/DataGrid.Resources&gt;
            &lt;/DataGrid&gt;
        &lt;/Grid&gt;

        &lt;Grid Grid.Row=&quot;2&quot; Margin=&quot;10,10,10,10&quot;&gt;
            &lt;Grid.ColumnDefinitions&gt;
                &lt;ColumnDefinition Width=&quot;200*&quot;/&gt;
                &lt;ColumnDefinition Width=&quot;580*&quot;/&gt;
            &lt;/Grid.ColumnDefinitions&gt;

            &lt;Grid Grid.Column=&quot;0&quot;&gt;
                &lt;Button Content=&quot;들여오기&quot; Width=&quot;100&quot; Height=&quot;40&quot; HorizontalAlignment=&quot;Left&quot; /&gt;
            &lt;/Grid&gt;

            &lt;Grid Grid.Column=&quot;1&quot; HorizontalAlignment=&quot;Right&quot;&gt;
                &lt;StackPanel Orientation=&quot;Horizontal&quot; VerticalAlignment=&quot;Center&quot;&gt;
                    &lt;Label Content=&quot;Go to&quot; /&gt;
                    &lt;TextBox Width=&quot;30&quot;/&gt;
                    &lt;Label Content=&quot;Page&quot; /&gt;
                    &lt;Button Content=&quot;Go&quot; Margin=&quot;0,0,5,0&quot;/&gt;
                    &lt;Button Content=&quot;Last Page&quot; Margin=&quot;0,0,5,0&quot;/&gt;
                    &lt;Button Content=&quot;Next Page&quot; /&gt;
                    &lt;Label Content=&quot;[total&quot; /&gt;
                    &lt;Label Content=&quot;1&quot; /&gt;
                    &lt;Label Content=&quot;page]&quot; /&gt;
                    &lt;Label Content=&quot;[current&quot; /&gt;
                    &lt;Label Content=&quot;1&quot; /&gt;
                    &lt;Label Content=&quot;page]&quot; /&gt;
                &lt;/StackPanel&gt;
            &lt;/Grid&gt;
        &lt;/Grid&gt;

    &lt;/Grid&gt;
&lt;/Page&gt;
</code></pre>
<h3 id="3-2-엑셀-파일만-찾을-수-있는-파일-탐색기">3-2. 엑셀 파일만 찾을 수 있는 파일 탐색기</h3>
<ol>
<li>ViewModel 폴더에 Page4ViewModel.cs를 생성한다.</li>
</ol>
<ul>
<li>RelayCommand, BaseViewModel 작성은 <a href="https://velog.io/@yu_oolong/C-WPF-Button%EC%9D%84-%EB%88%8C%EB%9F%AC-Frame%EC%97%90-Page-%EB%9D%84%EC%9A%B0%EA%B8%B0-1">내용</a> 참고</li>
</ul>
<p>파일 찾기 버튼을 누르면 파일 탐색기가 열려야 한다.
파일 탐색기는 <a href="https://learn.microsoft.com/ko-kr/dotnet/desktop/wpf/windows/how-to-open-common-system-dialog-box?view=netdesktop-7.0">OpenFileDialog</a> 클래스로 구현되는데 <a href="https://learn.microsoft.com/ko-kr/dotnet/api/system.windows.forms.filedialog.filter?view=windowsdesktop-7.0">FileDialog.Filter</a> 속성을 사용하여 확장자가 *.xls, *.xlsx인 엑셀 파일만 고를 수 있도록한다.</p>
<p>(Page4ViewModel.cs)</p>
<pre><code class="language-cs">namespace PracticeProject2.ViewModels
{
    internal class Page4ViewModel : BaseViewModel
    {
        public ICommand SuchFileCommand { get; set; }

        string _fileUrl;

        public string FileUrl
        {
            get { return _fileUrl; }
            set
            {
                _fileUrl = value;
                OnPropertyChanged(nameof(FileUrl));
            }
        }

        public Page4ViewModel()
        {
            SuchFileCommand = new RelayCommand(SuchFile);
        }

        private void SuchFile()
        {
            OpenFileDialog dialog = new OpenFileDialog();
            dialog.Filter = &quot;문서 파일 (*.xls,*xlsx)|*.xls;*.xlsx&quot;;
            if (dialog.ShowDialog() == true)
            {
                FileUrl = dialog.FileName;
            }
        }

    }
}</code></pre>
<ol start="2">
<li>Page4.xaml로 돌아와서 Button과 TextBox에 Binding하기<pre><code class="language-xaml">     &lt;Grid Grid.Row=&quot;0&quot; Margin=&quot;10,0,10,0&quot;&gt;
         &lt;StackPanel Orientation=&quot;Horizontal&quot; HorizontalAlignment=&quot;Center&quot; VerticalAlignment=&quot;Center&quot;&gt;
             &lt;Label Content=&quot;파일 이름&quot; /&gt;
             &lt;TextBox x:Name=&quot;fileUrlTextBox&quot; Width=&quot;450&quot; Margin=&quot;10,0,10,0&quot; Text=&quot;{Binding FileUrl}&quot;/&gt;
             &lt;Button Content=&quot;파일찾기&quot; Command=&quot;{Binding SuchFileCommand}&quot;/&gt;
         &lt;/StackPanel&gt;
     &lt;/Grid&gt;</code></pre>
실행해보면 파일 찾기 버튼을 눌러 파일 탐색기를 띄웠을 때 확장자가 *xls, *xlsx인 파일만 보이고, 파일을 고르면 해당 파일의 이름이 TextBox에 보여진다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/5c836b81-c651-4b7a-a0ae-a291315ace6c/image.png" alt="">
<img src="https://velog.velcdn.com/images/yu_oolong/post/b5975a53-bec6-4044-a729-cf4de29bfc7b/image.png" alt=""></li>
</ol>
<h3 id="3-3-excel-import-to-datagrid">3-3. Excel import to DataGrid</h3>
<p>다음은 들여오기 버튼을 눌러 파일 탐색기에서 고른 엑셀 파일의 내용을 DataGrid에 보여주어야 한다.
Command를 작성하기 전에 솔루션용 NuGet 패키지 관리자에서 Microsoft.Office.Interop.Excel을 다운로드 받는다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/2d2b7f40-6f12-4d8c-b070-4931dd45dc7d/image.png" alt="">
다운로드 후 추가로 참조 관리자-Com에서 Microsoft Office 16.0 Object Library를 체크&gt;확인한다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/74c21cc3-5fdc-4d7f-8bd6-db3bde394b73/image.png" alt=""></p>
<p>이후 Page4ViewModel.cs 에서 using문을 선언한다. 
using Excel = Microsoft.Office.Interop.Excel 
<img src="https://velog.velcdn.com/images/yu_oolong/post/ce9c3b2b-bb50-4482-b0a1-953b418cf751/image.png" alt=""></p>
<p>SuchFile() 메소드에서 파일 탐색기를 열어 가져온 파일의 이름을 FileUrl에 초기화했다.
ImportFile() 메소드는 DataTable을 생성하여 SuchFile() 메소드에서 초기화한 FileUrl의 엑셀 파일의 내용으로부터 데이터를 저장하고, Page4에 배치된 x:Name이 ExcelData인 DataGrid에 보여준다.</p>
<p>불러올 엑셀 파일의 내용은 이렇다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/f48b0099-9d37-46b7-aa9d-8547c9a68c1a/image.png" alt=""></p>
<p>(Page4ViewModel.cs)</p>
<pre><code class="language-cs">using Microsoft.Win32;
using PracticeProject2.Commands;
using PracticeProject2.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows;
using Excel = Microsoft.Office.Interop.Excel;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Data;
using System.Diagnostics.Eventing.Reader;

namespace PracticeProject2.ViewModels
{
    internal class Page4ViewModel : BaseViewModel
    {
        public ICommand SuchFileCommand { get; set; }
        public ICommand ImportFileCommand { get; set; }

        string _fileUrl;

        public string FileUrl
        {
            get { return _fileUrl; }
            set
            {
                _fileUrl = value;
                OnPropertyChanged(nameof(FileUrl));
            }
        }

        public Page4ViewModel()
        {
            SuchFileCommand = new RelayCommand(SuchFile);
            ImportFileCommand = new RelayCommand(ImportFile);
        }

        private void SuchFile()
        {
            OpenFileDialog dialog = new OpenFileDialog();
            dialog.Filter = &quot;문서 파일 (*.xls,*xlsx)|*.xls;*.xlsx&quot;;
            if (dialog.ShowDialog() == true)
            {
                FileUrl = dialog.FileName;
            }
        }

        private void ImportFile()
        {
            Page4 page4 = Page4.GetInstance();

            DataTable dt = new DataTable(); //엑셀 데이터를 위한 컨테이너
            DataRow dr;

            int row = 0;
            int column = 0;

            Excel.Application excelApp = null;
            Excel.Workbook workbook = null;
            Excel.Worksheet worksheet = null;

            try
            {
                excelApp = new Excel.Application();                             //엑셀 어플리케이션 생성
                workbook = excelApp.Workbooks.Open(FileUrl);                    //워크북 열기
                worksheet = workbook.Worksheets.get_Item(1) as Excel.Worksheet; //엑셀 첫 번째 워크시트 가져오기

                Excel.Range range = worksheet.UsedRange;                        //사용중인 셀 범위 가져오기

                //첫 행을 제목으로
                for (column = 1; column &lt;= range.Columns.Count; column++)
                {
                    //header = (range.Cells[1, column] as Excel.Range).Value2.ToString();
                    string str = (string)(range.Cells[1, column] as Excel.Range).Value2;
                    dt.Columns.Add(str, typeof(string));
                }

                //내용 데이터 가져오기
                int rowCounter;
                for (row = 2; row &lt;= range.Rows.Count; row++)
                {
                    dr = dt.NewRow();
                    rowCounter = 0;
                    for (column = 1; column &lt;= range.Columns.Count; column++)
                    {
                        if (range.Cells[row, column] != null &amp;&amp; range.Cells[row, column].Value2 != null)
                        {
                            dr[rowCounter] = range.Cells[row, column].Value2.ToString();
                        }
                        else
                        {
                            dr[row] = &quot;&quot;;
                        }
                        rowCounter++;
                    }
                    dt.Rows.Add(dr);
                }

                page4.ExcelData.ItemsSource = dt.DefaultView;

                workbook.Close();
                excelApp.Quit();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                return;
            }

        }
    }
}
</code></pre>
<p>이때 Page4의 GetInstance() 메소드는 Page4.xaml.cs에서 작성해주어야 한다.
GetInstance는 싱글톤 패턴으로 작성한 Page4의 객체이다.</p>
<blockquote>
<p>(+ 2023.09.26 날짜 형식 추가)
네 번째 컬럼이 &#39;날짜&#39;여서 if(column=4) 일때...
셀의 내용(string)-&gt;double로 형 변환하여 날짜 형식으로 행에 추가한다.</p>
</blockquote>
<pre><code class="language-cs">                //내용 데이터 가져오기
                int rowCounter; //This variable is used for row index number
                for (row = 2; row &lt;= range.Rows.Count; row++)
                {
                    dr = dt.NewRow(); //assign new row to DataTable
                    rowCounter = 0;
                    for (column = 1; column &lt;= range.Columns.Count; column++) //Loop for available column of excel data
                    {
                        //check if cell is empty
                        if (range.Cells[row, column] != null &amp;&amp; range.Cells[row, column].Value2 != null)
                        {
                            if (column == 4) //string to double 날짜 형식 변환
                            {
                                double val = Double.Parse(range.Cells[row, column].Value2.ToString());
                                DateTime conv = DateTime.FromOADate(val);
                                dr[rowCounter] = conv.ToString(&quot;yyyy/MM/dd&quot;);
                            }
                            else
                            {
                                dr[rowCounter] = range.Cells[row, column].Value2.ToString();
                            }
                        }
                        else
                        {
                            dr[row] = &quot;&quot;;
                        }
                        rowCounter++;
                    }
                    dt.Rows.Add(dr);
                }</code></pre>
<p>그럼 이런식으로...날짜 컬럼의 내용이 다섯자리 숫자가 아니라 보통 날짜 형식으로 나온다.
<img src="https://velog.velcdn.com/images/yu_oolong/post/d990522a-ba13-42da-ad70-d80bc49c70d2/image.png" alt=""></p>
<p>(Page4.xaml.cs)</p>
<pre><code class="language-cs">namespace PracticeProject2.Views
{
    /// &lt;summary&gt;
    /// Page4.xaml에 대한 상호 작용 논리
    /// &lt;/summary&gt;
    public partial class Page4 : Page
    {
        private static Page4 _Instance = null;

        public static Page4 GetInstance()
        {
            if (_Instance == null || !_Instance.IsLoaded)
            {
                _Instance = new Page4();
            }

            return _Instance;
        }

        public Page4()
        {
            InitializeComponent();

            _Instance = this;
        }
    }
}</code></pre>
<p>위의 내용까지 코드 작성을 마쳤다면 Page4.xaml에서 들여오기 버튼에 Command를 Binding 한다.</p>
<p>(Page4.xaml)</p>
<pre><code class="language-xaml">    &lt;Grid Grid.Column=&quot;0&quot;&gt;
        &lt;Button Content=&quot;들여오기&quot; Width=&quot;100&quot; Height=&quot;40&quot; HorizontalAlignment=&quot;Left&quot; Command=&quot;{Binding ImportFileCommand}&quot;/&gt;
    &lt;/Grid&gt;</code></pre>
<hr>
<h2 id="4-결과">4. 결과</h2>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/3062753d-cfce-4750-a494-8457c56afe6a/image.png" alt=""></p>
<hr>
<h2 id="5-참조">5. 참조</h2>
<ul>
<li><p><a href="https://nomadcoder.tistory.com/entry/WPF-Excel-%ED%8C%8C%EC%9D%BC-%EC%9D%BD%EC%96%B4%EC%84%9C-Datagrid-%EC%97%90-%EB%84%A3%EA%B8%B0-Excel-Import">https://nomadcoder.tistory.com/entry/WPF-Excel-%ED%8C%8C%EC%9D%BC-%EC%9D%BD%EC%96%B4%EC%84%9C-Datagrid-%EC%97%90-%EB%84%A3%EA%B8%B0-Excel-Import</a></p>
</li>
<li><p><a href="https://velog.io/@ansalstmd/C-%EC%95%8C%EC%95%84%EA%B0%80%EA%B8%B0-3.-C-Excel-Import-Export">https://velog.io/@ansalstmd/C-알아가기-3.-C-Excel-Import-Export</a></p>
</li>
<li><p><a href="https://legacy.tistory.com/101">https://legacy.tistory.com/101</a></p>
</li>
<li><p><a href="https://www.freecodespot.com/blog/csharp-import-excel/">https://www.freecodespot.com/blog/csharp-import-excel/</a></p>
</li>
<li><p><a href="https://learn.microsoft.com/ko-kr/dotnet/desktop/wpf/windows/how-to-open-common-system-dialog-box?view=netdesktop-7.0">https://learn.microsoft.com/ko-kr/dotnet/desktop/wpf/windows/how-to-open-common-system-dialog-box?view=netdesktop-7.0</a></p>
</li>
<li><p><a href="https://learn.microsoft.com/ko-kr/dotnet/api/system.windows.forms.filedialog.filter?view=windowsdesktop-7.0">https://learn.microsoft.com/ko-kr/dotnet/api/system.windows.forms.filedialog.filter?view=windowsdesktop-7.0</a></p>
</li>
<li><p><a href="https://ghostweb.tistory.com/954">https://ghostweb.tistory.com/954</a></p>
</li>
<li><p><a href="https://learn.microsoft.com/en-us/answers/questions/939640/how-to-setup-excel-interop-for-exccel-365">https://learn.microsoft.com/en-us/answers/questions/939640/how-to-setup-excel-interop-for-exccel-365</a></p>
</li>
<li><p><a href="https://cwkcw.tistory.com/m/307/comments">https://cwkcw.tistory.com/m/307/comments</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C# WPF] Behavior - InvokeCommandAction]]></title>
            <link>https://velog.io/@yu_oolong/C-WPF-Behavior-InvokeCommandAction</link>
            <guid>https://velog.io/@yu_oolong/C-WPF-Behavior-InvokeCommandAction</guid>
            <pubDate>Mon, 18 Sep 2023 05:44:54 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배경">1. 배경</h2>
<blockquote>
<p>코드 비하인드가 아닌 MVVM 패턴을 지키며 컨트롤의 이벤트를 사용하는 방법
<a href="https://velog.io/@yu_oolong/C-WPF-Button%EC%9D%84-%EB%88%8C%EB%9F%AC-Frame%EC%97%90-Page-%EB%9D%84%EC%9A%B0%EA%B8%B0-2">https://velog.io/@yu_oolong/C-WPF-Button을-눌러-Frame에-Page-띄우기-2</a> 와 같은 솔루션에서 진행</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>VisualStudio 2022</li>
</ul>
<hr>
<h2 id="3-내용">3. 내용</h2>
<h3 id="3-1-nuget-패키지-관리에서-microsoftxamlbehaviorswpf-다운로드">3-1. NuGet 패키지 관리에서 Microsoft.Xaml.Behaviors.Wpf 다운로드</h3>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/2773e69a-757b-45af-bf08-b0105e2bba16/image.png" alt=""></p>
<h3 id="3-2-icommand를-상속-받는-relaycommandcs-작성">3-2. ICommand를 상속 받는 RelayCommand.cs 작성</h3>
<pre><code class="language-cs">using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace PackagingSystem.Commands
{
    public class RelayCommand : ICommand
    {
        private readonly Action _execute;
        private readonly Func&lt;bool&gt; _canExecute;

        public RelayCommand(Action execute, Func&lt;bool&gt; canExecute = null)
        {
            _execute = execute ?? throw new ArgumentNullException(nameof(execute));
            _canExecute = canExecute;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public bool CanExecute(object parameter) =&gt; _canExecute == null || _canExecute();

        public void Execute(object parameter) =&gt; _execute();
    }

    public class RelayCommand&lt;T&gt; : ICommand
    {
        readonly Action&lt;T&gt; _execute = null;
        readonly Predicate&lt;T&gt; _canExecute = null;

        public RelayCommand(Action&lt;T&gt; execute, Predicate&lt;T&gt; canExecute = null)
        {
            _execute = execute ?? throw new ArgumentNullException(nameof(execute));
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute?.Invoke((T)parameter) ?? true;
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        public void Execute(object parameter)
        {
            _execute((T)parameter);
        }
    }
}
</code></pre>
<h3 id="3-3-page1xaml이-로드-될-시-동작하는-behavior를-viewmodel에-작성">3-3. Page1.xaml이 로드 될 시 동작하는 Behavior를 ViewModel에 작성</h3>
<ul>
<li>(Page1ViewModel.cs) 
BaseViewModel을 상속 받는다.<pre><code class="language-cs">using PackagingSystem.Commands;
using PackagingSystem.ViewModels;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
</code></pre>
</li>
</ul>
<p>namespace PracticeProject.ViewModels
{
    internal class Page1ViewModel : BaseViewModel
    {
        public ICommand Page1LoadedCommand { get; set; }</p>
<pre><code>    public Page1ViewModel() 
    {
        Page1LoadedCommand = new RelayCommand(Page1Loaded);
    }

    public void Page1Loaded()
    {
        MessageBox.Show(&quot;Page Loaded&quot;);
    }
}</code></pre><p>}</p>
<pre><code>
### 3-4. Page1.xaml에 namespace 추가하고 DataContext 작성

- (Page1.xaml)
![](https://velog.velcdn.com/images/yu_oolong/post/ab5d90b1-5fef-4c75-927d-82c6c87b88bc/image.png)


```xaml
&lt;Page x:Class=&quot;PracticeProject.Views.Page1&quot;
      xmlns=&quot;http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;
      xmlns:x=&quot;http://schemas.microsoft.com/winfx/2006/xaml&quot;
      xmlns:mc=&quot;http://schemas.openxmlformats.org/markup-compatibility/2006&quot; 
      xmlns:d=&quot;http://schemas.microsoft.com/expression/blend/2008&quot; 
      xmlns:local=&quot;clr-namespace:PracticeProject.Views&quot;
      xmlns:vm=&quot;clr-namespace:PracticeProject.ViewModels&quot;
      xmlns:i=&quot;http://schemas.microsoft.com/xaml/behaviors&quot;
      mc:Ignorable=&quot;d&quot; 
      d:DesignHeight=&quot;450&quot; d:DesignWidth=&quot;800&quot;
      Title=&quot;Page1&quot; Background=&quot;Gray&quot;&gt;

    &lt;Page.DataContext&gt;
        &lt;vm:Page1ViewModel /&gt;
    &lt;/Page.DataContext&gt;

    &lt;i:Interaction.Triggers&gt;
        &lt;i:EventTrigger EventName=&quot;Loaded&quot;&gt;
            &lt;i:InvokeCommandAction Command=&quot;{Binding Page1LoadedCommand}&quot; /&gt;
        &lt;/i:EventTrigger&gt;
    &lt;/i:Interaction.Triggers&gt;

    &lt;Grid&gt;

    &lt;/Grid&gt;
&lt;/Page&gt;
</code></pre><hr>
<h2 id="4-결과">4. 결과</h2>
<blockquote>
<p>앞서 Menu1을 누르면 Page1이 로드되게 했었는데 Behavior 작성을 추가하여 Page1이 로드 되기 직전에 MessageBox가 호출된다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/5512cb9e-c0e7-4603-9769-c7b8bfea9581/image.png" alt=""></p>
<blockquote>
<p>MessageBox의 확인 버튼을 누르면 Page1이 로드 된다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/25bc9631-7139-486d-845d-aa7511c3a504/image.png" alt=""></p>
<hr>
<h2 id="5-참조">5. 참조</h2>
<p><strong>Behavior</strong></p>
<ul>
<li><a href="https://github.com/Microsoft/XamlBehaviorsWpf">https://github.com/Microsoft/XamlBehaviorsWpf</a></li>
<li><a href="https://stpetrus27.wordpress.com/2019/02/12/c-wpf-mvvm-microsoft-xaml-behaviors-and-eventtrigger-1/">https://stpetrus27.wordpress.com/2019/02/12/c-wpf-mvvm-microsoft-xaml-behaviors-and-eventtrigger-1/</a></li>
<li><a href="https://m.blog.naver.com/vactorman/221176866353">https://m.blog.naver.com/vactorman/221176866353</a></li>
<li><a href="https://gomnezip.tistory.com/442">https://gomnezip.tistory.com/442</a></li>
<li><a href="https://kaki104.tistory.com/840">https://kaki104.tistory.com/840</a></li>
<li><a href="https://kaki104.tistory.com/841">https://kaki104.tistory.com/841</a></li>
</ul>
<p><strong>Loaded 이벤트</strong></p>
<ul>
<li><a href="https://truepia.tistory.com/278">https://truepia.tistory.com/278</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C# WPF] Xceed WPF Toolkit 설치]]></title>
            <link>https://velog.io/@yu_oolong/C-WPF-Xceed-WPF-Toolkit-%EC%84%A4%EC%B9%98</link>
            <guid>https://velog.io/@yu_oolong/C-WPF-Xceed-WPF-Toolkit-%EC%84%A4%EC%B9%98</guid>
            <pubDate>Thu, 14 Sep 2023 23:42:11 GMT</pubDate>
            <description><![CDATA[<h3 id="1-xceed-extended-toolkit-for-wpf">1. Xceed Extended Toolkit for WPF</h3>
<ul>
<li><a href="https://xceed.com/en/our-products/product/toolkit-plus-for-wpf/">https://xceed.com/en/our-products/product/toolkit-plus-for-wpf/</a></li>
<li><a href="https://github.com/xceedsoftware/wpftoolkit">https://github.com/xceedsoftware/wpftoolkit</a></li>
<li><a href="https://github.com/xceedsoftware/wpftoolkit/wiki/Xceed-Toolkit-Plus-for-WPF">https://github.com/xceedsoftware/wpftoolkit/wiki/Xceed-Toolkit-Plus-for-WPF</a></li>
</ul>
<blockquote>
<p>GitHub의 내용에서는 무료 Toolkit은 v4.0.0부터 비상업적 용도에 따라 제공되고 유료는 Plus Edtion이라는 것을 제공한다고 한다.</p>
</blockquote>
<hr>
<h3 id="2-visual-studio에서-설치">2. Visual Studio에서 설치</h3>
<h4 id="2-1-솔루션용-nuget-패키지-관리에서-extendedwpftoolkit-설치">2-1. 솔루션용 NuGet 패키지 관리에서 Extended.Wpf.ToolKit 설치</h4>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/151d297a-ea87-4858-8a31-d6348a395992/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/ab84e791-9c15-4f6f-91c2-0d9679d8452b/image.png" alt=""></p>
<h4 id="2-2-패키지-관리자-콘솔에서-install">2-2. 패키지 관리자 콘솔에서 install</h4>
<ol>
<li>install-Package Extended.Wpf.Toolkit 입력 후 Enter</li>
</ol>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/f0472aea-9c17-4987-a05e-c889ceeb7269/image.png" alt=""></p>
<ol start="2">
<li>App.config 파일에 아래의 내용 복사-붙여넣기</li>
</ol>
<pre><code>&lt;runtime&gt;
    &lt;assemblyBinding xmlns=&quot;urn:schemas-microsoft-com:asm.v1&quot;&gt;
      &lt;dependentAssembly&gt;
        &lt;assemblyIdentity name=&quot;Xceed.Wpf.Toolkit&quot; publicKeyToken=&quot;3e4669d2f30244f4&quot; culture=&quot;neutral&quot; /&gt;
        &lt;bindingRedirect oldVersion=&quot;0.0.0.0-4.5.0.0&quot; newVersion=&quot;4.5.0.0&quot; /&gt;
      &lt;/dependentAssembly&gt;
    &lt;/assemblyBinding&gt;
  &lt;/runtime&gt;</code></pre><ol start="3">
<li>도구 상자 항목에서 WPF 구성 요소 탭에서 찾아보기 클릭 후 
프로젝트 경로/packages/Extend.Wpf.Toolkit.../lib/net40 내부의 Xceed.Wpf.Toolkit.dll을 열기 -&gt; 확인</li>
</ol>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/e8eda7f9-7878-48d6-af97-2ee49200d599/image.png" alt=""></p>
<h3 id="3-솔루션-빌드-후-도구-상자에서-확인">3. 솔루션 빌드 후 도구 상자에서 확인</h3>
<p><img src="https://velog.velcdn.com/images/yu_oolong/post/937aee80-08e6-435a-84cd-d088f5102d12/image.png" alt=""></p>
<hr>
<blockquote>
<p>만드느라 애먹었던 TextBox, PasswordBox의 PlaceHorder...같은 것들도 구현이 되어있어 편리하다. 🥹</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C# WPF] C#에서의 싱글톤 패턴]]></title>
            <link>https://velog.io/@yu_oolong/C-WPF-C%EC%97%90%EC%84%9C%EC%9D%98-%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@yu_oolong/C-WPF-C%EC%97%90%EC%84%9C%EC%9D%98-%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Thu, 14 Sep 2023 06:25:55 GMT</pubDate>
            <description><![CDATA[<h2 id="1-싱글톤-패턴이란-사용하는-이유-단점">1. 싱글톤 패턴이란, 사용하는 이유, 단점</h2>
<blockquote>
<p>생성할 수 있는 객체의 인스턴스를 하나로 한정하여 이미 생성된 인스턴스를 불러내 사용하기 때문에 메모리를 절약할 수 있다.
그러나 싱글톤 또한 static이기 때문에 남발하는 것이 좋지는 않다.</p>
</blockquote>
<hr>
<h2 id="2-개발환경">2. 개발환경</h2>
<ul>
<li>VisualStudio 2022</li>
</ul>
<hr>
<h2 id="3-code">3. Code</h2>
<ol>
<li>싱글톤 패턴으로 작성할 클래스의 private static 변수</li>
<li>만들어진 인스턴스를 외부에서 접근할 수 있는 private static 메소드</li>
<li>외부에서 인스턴스를 생성할 수 없도록 생성자의 타입을 private으로</li>
</ol>
<pre><code class="language-cs">
  private static ExSingleton _Instance = null;

  public static ExSingleton GetInstance()
  {
      if (_Instance == null)
      {
          _Instance = new ExSingleton();
      }
      return _Instance;
  }

  private ExSingleton()
  {

  }</code></pre>
<hr>
]]></description>
        </item>
    </channel>
</rss>