훈스닷넷에 PictureBox위에 1cm 간격으로 눈금을 그리고 싶어하는 질문이 있어서

 

데모 프로젝트를 작성해보았습니다.

 

 

 

코드는 아래와 같습니다.

 

 

public partial class Form1 : Form

{

    /// <summary>

    /// 1pixel에 해당하는 센치

    /// </summary>

    const float OnePixelCentimetre = 37.79f;

    /// <summary>

    /// 수직라인 길이

    /// </summary>

    const int VerticalLineHeight = 50;

    /// <summary>

    /// 그리드 라인 색상

    /// </summary>

    readonly Pen gridLineColor;

 

    public Form1()

    {

        InitializeComponent();

        this.pictureBox1.Image = Image.FromFile("./Chrysanthemum.jpg");

 

        this.pictureBox1.Paint += PictureBox1_PaintEventHandler;

        this.SizeChanged += Form1_SizeChangedEventHandler;

        this.gridLineColor = Pens.Yellow;

    }

 

    /// <summary>

    /// 폼의 사이즈 변경 이벤트 처리기

    /// </summary>

    /// <param name="sender"></param>

    /// <param name="e"></param>

    private void Form1_SizeChangedEventHandler(object sender, EventArgs e)

    {

        this.pictureBox1.Invalidate();

    }

 

    /// <summary>

    /// PictureBox Paint 이벤트 처리기

    /// </summary>

    /// <param name="sender"></param>

    /// <param name="e"></param>

    private void PictureBox1_PaintEventHandler(object sender, PaintEventArgs e)

    {

        PictureBox pb = (PictureBox)sender;

        Graphics g = e.Graphics;

 

        //Picturebox의 중앙 위치

        float centerYPosition = pb.Height / 2.0f;

 

        //수평 라인 그리기

        g.DrawLine(gridLineColor, 0f, centerYPosition, pb.Width, centerYPosition);

 

        //현재 Picturbox에서 1cm 개수

        double cmStep = pb.Width / OnePixelCentimetre;

 

        //세로 라인의 절반

        int verticalLineHeightHalf = VerticalLineHeight / 2;

 

        //수직 라인 그리기

        for (float i = 0; i < pb.Width; i += OnePixelCentimetre)

        {

            PointF beginPoint = new PointF(i, centerYPosition - verticalLineHeightHalf);

            PointF endPoint = new PointF(i, centerYPosition + verticalLineHeightHalf);

            g.DrawLine(gridLineColor, beginPoint, endPoint);

        }

    }

}

 

 

 

프로젝트(vs2015 작성) : HowToDrawOnPicturebox.zip

 

개발 중인 프로젝트에서 상용 컴퍼넌트를 이용하다보니 제작한 라이브러리 포함하여 dll이 30개 정도 되었다.

 

고객님께서는 dll이 너무 많아 복잡하다며 실행 파일 한개만을 원하셨다.

 

 

 

제프리 리쳐 형님과 같이 동적으로 어셈블리를 로드할까 했는데....좀 더 검색해보았다.

 

 

AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => {

    String resourceName = AssemblyLoadingAndReflection. +

        new AssemblyName(args.Name).Name + .dll;

    using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))

    {

        Byte[] assemblyData = new Byte[stream.Length];

        stream.Read(assemblyData, 0, assemblyData.Length);

        return Assembly.Load(assemblyData);

    }

}; 

참조 : https://blogs.msdn.microsoft.com/microsoft_press/2010/02/03/jeffrey-richter-excerpt-2-from-clr-via-c-third-edition/

 

 

 

 

결국 Costumra.Fody 를 찾게 되었다.

 

자동으로 exe 안에 라이브러리를 넣어준다. 압축까지 지원해준다.

 

Nuget을 통하여 설치하면 "FodyWeavers.xml" 파일이 생성된다.

 

해당 xml 파일을 이용해 정교한 설정이 가능하다. (압축여부, Debug심볼첨부여부 등등...)

 

https://github.com/Fody/Costura

 

 

 

 

 

압축하였을 때는 .zip 확장자로 exe 파일에 삽입되어있다.

 

[압축하지 않은 상태]

 

 

 

[압축한 상태]

'.Net > C#' 카테고리의 다른 글

C# 리플렉션을 이용한 Delegate 넘기기  (0) 2016.05.11
C# Undo Redo 기능 구현하기  (3) 2015.10.20
C# 원문자 구하기  (0) 2015.10.05
SerialPort Read Blocking 시키기  (0) 2015.08.05
문자열 비교 테스트 (대소문자 무시)  (0) 2015.07.02

A.exe에서 B.dll을 컴파일시 참조하지 않고  동적으로 참조할 때,

 

B.dll에 있는 특정 클래스 메서드의 파라미터(Delegate)를 넘길때 방법입니다. 

 

리플렉션을 이용해서 대리자 개체를 생성 후 Invoke 파라미터에 넘겨주었습니다.

 

A.exe

 

 

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Reflection;

using System.Text;

using System.Threading.Tasks;

using System.Windows.Forms;

 

namespace DelegateReflaction

{

    public partial class Form1 : Form

    {

        public Form1()

        {

            InitializeComponent();

            InvokeTest();

        }

 

        private void InvokeTest()

        {

            string dllFilePath = @"D:\Work\Project\Test\DelegateReflaction\TestLibrary\bin\Debug\TestLibrary.dll";

            Assembly assembly = Assembly.LoadFile(dllFilePath);

 

            Type formType = assembly.GetType("TestLibrary.ChiForm");

 

            //Child 델리게이트 타입 가져오기

            Type deleType = assembly.GetTypes().Single(a => a.Name == "CallbackDelegate");

 

 

            //현재 클래스의 Callback 메서드 정보 가져오기

            MethodInfo callBackMethodInfo = this.GetType().GetMethod("Callback", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(string) }, null);

 

            //Child CallbackDelegate Delegate를 이용해서 Deleagte 생성

            Delegate callbackMethod = Delegate.CreateDelegate(deleType, this, callBackMethodInfo);

 

            //Child 폼 생성

            Form form = Activator.CreateInstance(formType) as Form;

 

            //Child DoWork 메서드 가져오기

            MethodInfo doworkMethodInfo = formType.GetMethod("DoWork");

 

            //파라미터 생성

            object[] parameters = new object[2];

            parameters[0] = "test~";

            parameters[1] = callbackMethod;

 

            //Child Dowork 메서드 호출

            doworkMethodInfo.Invoke(form, parameters);

 

            form.Show();

        }

 

        void Callback(string msg)

        {

            MessageBox.Show(msg);

        }

    }

}

  

 

 

B.dll

 

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

using System.Windows.Forms;

 

namespace TestLibrary

{

    public class ChiForm : Form

    {

        public delegate void CallbackDelegate(string status);

 

        public ChiForm()

        {

            InitializeComponent();

        }

 

        public void DoWork(string param, CallbackDelegate callback)

        {

            callback("status");

        }

 

        private void InitializeComponent()

        {

            this.SuspendLayout();

            //

            // ChiForm

            //

            this.ClientSize = new System.Drawing.Size(284, 262);

            this.Name = "ChiForm";

            this.Text = "ChildForm";

            this.ResumeLayout(false);

 

        }

    }

} 

 

 

프로젝트 (vs2015 작성) :  DelegateReflaction.zip

 

'.Net > C#' 카테고리의 다른 글

C# 실행파일 안에 DLL 넣기  (0) 2016.05.16
C# Undo Redo 기능 구현하기  (3) 2015.10.20
C# 원문자 구하기  (0) 2015.10.05
SerialPort Read Blocking 시키기  (0) 2015.08.05
문자열 비교 테스트 (대소문자 무시)  (0) 2015.07.02

Undo 기능에 쓰일 스택과 Redo 기능에 쓰일 스택을 이용해서 Undo, Redo 기능을 구현해보았다.

 

UndoRedoHistory 클래스에서는 특정 타입의 상태를 저장할 수 있고, Undo, Redo를 통해 상태 값을 가져온다.

 

 

/// <summary>

    /// Undo Redo 내역을 기록하는 클래스

    /// </summary>

    /// <typeparam name="T">기록할 타입</typeparam>

    public class UndoRedoHistory<T>

    {

        const int DefaultUndoCount = 10;

        RimitedStack<T> undoStack;

        RimitedStack<T> redoStack;

 

        /// <summary>

        /// Undo 기능을 사용할 수 있는지 여부를 가져온다.

        /// </summary>

        public bool IsCanUndo

        {

            get

            {

                //맨 초기 상태때문에 1보다 커야한다.

                return this.undoStack.Count > 1;

            }

        }

 

        /// <summary>

        /// Redo 기능을 사용할 수 있는지 여부를 가져온다.

        /// </summary>

        public bool IsCanRedo

        {

            get { return this.redoStack.Count > 0; }

        }

 

        public UndoRedoHistory()

            :this(DefaultUndoCount)

        {

 

        }

 

        public UndoRedoHistory(int defaultUndoCount)

        {

            undoStack = new RimitedStack<T>(defaultUndoCount);

            redoStack = new RimitedStack<T>(defaultUndoCount);

        }

 

        /// <summary>

        /// 이전 상태를 가져온다.

        /// </summary>

        /// <returns></returns>

        public T Undo()

        {

            T state = this.undoStack.Pop();

            this.redoStack.Push(state);

            return this.undoStack.Peek();

        }

 

        /// <summary>

        /// 이후 상태를 가져온다.

        /// </summary>

        /// <returns></returns>

        public T Redo()

        {

            T state = this.redoStack.Pop();

            this.undoStack.Push(state);

            return state;

        }

 

        /// <summary>

        /// 상태를 추가한다.

        /// </summary>

        /// <param name="state"></param>

        public void AddState(T state)

        {

            this.undoStack.Push(state);

            this.redoStack.Clear();

        }

 

        /// <summary>

        /// 상태를 모두 제거한다.

        /// </summary>

        internal void Clear()

        {

            this.undoStack.Clear();

            this.redoStack.Clear();

        }

    }

 

 

 

 

ReimetedStack은 개수 제한이 있는 스택이다.

 

개수 제한이 걸렸을 때, 맨 처음 삽입한 상태를 제거해야하므로, 내부 자료구조는 사실 Stack이 아닌 List로 구현해놓았다.

 

/// <summary>

/// 개수 제한이 있는 스택 클래스

/// </summary>

internal class LimitedStack<T>

{

    List<T> list = new List<T>();

    readonly int capacity;

    /// <summary>

    /// 개수를 가져온다.

    /// </summary>

    public int Count

    {

        get { return this.list.Count; }

    }

 

    /// <summary>

    /// 생성자

    /// </summary>

    /// <param name="capacity"></param>

    public LimitedStack(int capacity)

    {

        this.capacity = capacity;

    }

 

    /// <summary>

    /// 맨위의 개체를 반환하고 제거한다.

    /// </summary>

    /// <returns></returns>

    internal T Pop()

    {

        T t = this.list[0];

        this.list.RemoveAt(0);

        return t;

    }

 

    /// <summary>

    /// 개체를 맨위에 삽입한다.

    /// </summary>

    /// <param name="state"></param>

    internal void Push(T state)

    {

        this.list.Insert(0, state);

        if (this.list.Count > capacity)

        {

            this.list.RemoveAt(this.list.Count - 1);

        }

    }

 

    /// <summary>

    /// 맨위의 개체를 제거하지 않고 반환한다.

    /// </summary>

    /// <returns></returns>

    internal T Peek()

    {

        return this.list[0];

    }

 

    /// <summary>

    /// 개체를 모두 제거한다.

    /// </summary>

    internal void Clear()

    {

        this.list.Clear();

    }

}

 

 

 

 

아래는 텍스트 박스의 내용을 저장하는 기능을 구현한 데모 코드이다.

 

public partial class Form1 : Form

{

UndoRedoHistory<string> undoRedoHistory = new UndoRedoHistory<string>(10);

 

public Form1()

{

    InitializeComponent();

    SaveCurrentState();

    this.textBox1.KeyDown += TextBox1_KeyDown;

}

 

private void TextBox1_KeyDown(object sender, KeyEventArgs e)

{

    Keys keyData = e.KeyData;

    if ((keyData & Keys.Control) == Keys.Control)

    {

        if ((keyData & Keys.Z) == Keys.Z)

        {

            this.Undo();

        }

        else if ((keyData & Keys.Y) == Keys.Y)

        {

            this.Redo();

        }

    }

}

 

private void Redo()

{

    if (this.undoRedoHistory.IsCanRedo)

    {

        this.textBox1.Text = this.undoRedoHistory.Redo();

    }

}

 

private void Undo()

{

    if (this.undoRedoHistory.IsCanUndo)

    {

        this.textBox1.Text = this.undoRedoHistory.Undo();

    }

}

 

private void btnSave_Click(object sender, EventArgs e)

{

    this.SaveCurrentState();

}

 

/// <summary>

/// 현재 상태를 저장한다.

/// </summary>

private void SaveCurrentState()

{

    undoRedoHistory.AddState(this.textBox1.Text);

}

 

 

 

샘플 프로젝트 :KDYFramework.UndoRedo.zip

 

 

'.Net > C#' 카테고리의 다른 글

C# 실행파일 안에 DLL 넣기  (0) 2016.05.16
C# 리플렉션을 이용한 Delegate 넘기기  (0) 2016.05.11
C# 원문자 구하기  (0) 2015.10.05
SerialPort Read Blocking 시키기  (0) 2015.08.05
문자열 비교 테스트 (대소문자 무시)  (0) 2015.07.02

 

 

위와 같이 PictureBox의 이미지를 투명하게 만드는 코드이다.

 

1. 불러올 파일의 경로의 Image 개체를 생성 

 

2. ChangeOpacity 메서드를 통해 이미지의 알파값을 변경하여 새로운 비트맵 개체 생성

 

3. 생성한 비트맵 개체를 PictureBox Image 속성으로 설정

 

4. 생성한 Image 개체 제거

 

 

public partial class Form1 : Form

{

    public Form1()

    {

        InitializeComponent();

    }

 

    private void btnLoad_Click(object sender, EventArgs e)

    {

        OpenFileDialog of = new OpenFileDialog();

 

        if (of.ShowDialog() == DialogResult.OK)

        {

            try

            {

                this.SetImage(of.FileName);

            }

            catch (Exception ex)

            {

                MessageBox.Show(ex.Message);

            }

        }

    }

 

    private void SetImage(string fileName)

    {

        try

        {

            Image img = Image.FromFile(fileName);

            this.pictureBox1.Image = ChangeOpacity(img, 0.5f);

            img.Dispose();

        }

        catch (Exception ex)

        {

            throw ex;

        }

    }

 

    /// <summary>

    /// 해당 이미지의 투명도를 변경한다.

    /// </summary>

    /// <param name="img"></param>

    /// <param name="opacityvalue"></param>

    /// <returns></returns>

    public Bitmap ChangeOpacity(Image img, float opacityvalue)

    {

        Bitmap bmp = new Bitmap(img.Width, img.Height);

        Graphics graphics = Graphics.FromImage(bmp);

        ColorMatrix colormatrix = new ColorMatrix();

        colormatrix.Matrix33 = opacityvalue;

        ImageAttributes imgAttribute = new ImageAttributes();

        imgAttribute.SetColorMatrix(colormatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

        graphics.DrawImage(img, new Rectangle(0, 0, bmp.Width, bmp.Height), 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, imgAttribute);

        graphics.Dispose();

        return bmp;

    }

}

 

 

 

vs2015 프로젝트 :  PictureBox투명도.zip

 

출처 : https://raviranjankr.wordpress.com/2011/05/25/change-opacity-of-image-in-c/

 

 

 

답변 :

 

t's actually not a bug, but may seem such.

In stacked or segmented mode, vertical zooming currently works as follows:
axis.Maximum and Minimum are multiplied or divided (depending on in/out zoom) by ViewXY.ZoomPanOptions.ZoomFactor value. That happens when zooming with Ctrl + left or right mouse button click. Wheel is related to factor this as well.

As your Y range is not symmetrical to 0 level, the new zoomed scale is not suitable for your data. And in your code you have enabled series.LimitYToStackSegment, so it shows up very clearly and ugly.

There's a way to override zooming with custom logic like this, by using BeforeZooming event handler:

m_chart.ViewXY.BeforeZooming += ViewXY_BeforeZooming;


//Make symmetrical zooming around old Y range center point.
void ViewXY_BeforeZooming(System.Collections.Generic.List<RangeChangeInfo> xRanges,
System.Collections.Generic.List<RangeChangeInfo> yRanges, bool byWheel, ref bool cancel)
{
m_chart.BeginUpdate();
cancel = true;
foreach(RangeChangeInfo rci in yRanges)
{
double yMid = (rci.OldMin + rci.OldMax) / 2.0;
double newYRange = rci.NewMax - rci.NewMin;

rci.Axis.SetRange(yMid-newYRange/2.0, yMid + newYRange/2.0);
}
m_chart.EndUpdate();
}


Maybe we should change the built-in zooming to use this kind of "symmetrical-to-Y-range-mid-point" instead of the current approach?

해당 숫자에해당하는 원문자를 반환하는 메서드입니다. 

 

 

/// <summary>

/// 해당 숫자에 해당하는 원문자열을 반환한다.

/// </summary>

/// <param name="number">변환하고자 하는 숫자</param>

/// <returns>변환된 원문자열</returns>

private string ConvertCombinumerals(int number)

{

    char c = ' ';

    if (number == 0)

    {

        c = (char)9450;

    }

    else if (number >= 1 && number <= 20)

    {

        c = (char)(9312 + (number - 1));

    }

    else if (number >= 21 && number <= 35)

    {

        c = (char)(12881 + (number - 21));

    }

    else if (number >= 36 && number <= 50)

    {

        c = (char)(12977 + (number - 36));

    }

    return c.ToString();

}

 

 

 

아래는 원문자에 해당하는 유니코드 값입니다.

 

⓪ 9450
① 9312
② 9313
③ 9314
④ 9315
⑤ 9316
⑥ 9317
⑦ 9318
⑧ 9319
⑨ 9320
⑩ 9321
⑪ 9322
⑫ 9323
⑬ 9324
⑭ 9325
⑮ 9326
⑯ 9327
⑰ 9328
⑱ 9329
⑲ 9330
⑳ 9331
㉑ 12881
㉒ 12882
㉓ 12883
㉔ 12884
㉕ 12885
㉖ 12886
㉗ 12887
㉘ 12888
㉙ 12889
㉚ 12890
㉛ 12891
㉜ 12892
㉝ 12893
㉞ 12894
㉟ 12895
㊱ 12977
㊲ 12978
㊳ 12979
㊴ 12980
㊵ 12981
㊶ 12982
㊷ 12983
㊸ 12984
㊹ 12985
㊺ 12986
㊻ 12987
㊼ 12988
㊽ 12989
㊾ 12990
㊿ 12991

닷넷의 SerialPort 클래스를 이용해 통신을 하다보니 문제가 발생하였다.

 

Read 메서드를 이용해 특정 바이트 수만큼 읽는 로직을 작성하였는데,

 

예를들어 4바이트를 Read하면 4바이트 이하여도 Read 메서드의 Blocking이 풀리는 현상이다.

 

사실 버그는 아니고......msdn을 살펴보니 수신 버퍼가 비어있지 않다면 해당  Read 메서드의 인자인  count 이하만큼

 

읽어버리는 것을 알았다.

 

아래와 같이 처리해주면 count만큼 수신할 수 있다.

 

/// <summary>

/// 데이터를 수신한다.

/// </summary>

/// <param name="buf">수신할 버퍼</param>

/// <param name="offset">데이터를 저장할 위치</param>

/// <param name="count">데이터 저장 바이트 수</param>

/// <returns></returns>

public int Receive(byte[] buf, int offset, int count)

{

    if (this.serialPort != null)

    {

        int bytesExpected = count, bytesRead = 0;

        while (bytesExpected > 0 && (bytesRead = serialPort.Read(buf, offset, bytesExpected)) > 0)

        {

            offset += bytesRead;

            bytesExpected -= bytesRead;

        }

        return count;

    }

    else

        return 0;

} 

 

 

 

 

 

VisualStudio에서 컨트롤 배치 할때와 같이 도구상자에서 드래그&드롭하여 배치하는 예제이다.

 

개발 내용 :

1. 디자인 모드일때 투명한 패널을 앞에두어서 Button,TextBox와 같이 클릭에 반응하는 컨트롤들이 반응하지 않게두었다.

2. Painted 이벤트를 두어서 자식 컨트롤 최상단에 Graphics 개체를 이용해 Drawing을 할수있게 하였다.

 

 

컨트롤드래그앤드랍추가및크기조절.zip

 

소스

 

string src = "abcd";

string dest = "ABCD";

int maxIter = 10000;

int matchCount = 0;

 

Stopwatch sw = Stopwatch.StartNew();

matchCount = 0;

for (int i = 0; i < maxIter; i++)

{

    if (src.ToLower() == dest.ToLower())

        matchCount++;

}

sw.Stop();

if (matchCount != maxIter)

    return;

Console.WriteLine("Lower Equal " + sw.Elapsed.ToString());

 

sw = Stopwatch.StartNew();

matchCount = 0;

for (int i = 0; i < maxIter; i++)

{

    if (src.ToUpper() == dest.ToUpper())

        matchCount++;

}

sw.Stop();

if (matchCount != maxIter)

    return;

Console.WriteLine("Upper Equal " + sw.Elapsed.ToString());

 

sw = Stopwatch.StartNew();

matchCount = 0;

for (int i = 0; i < maxIter; i++)

{

    if (src.Equals(dest, StringComparison.InvariantCultureIgnoreCase))

        matchCount++;

}

sw.Stop();

if (matchCount != maxIter)

    return;

Console.WriteLine("StringComparison.Ordinal InvariantCultureIgnoreCase " + sw.Elapsed.ToString());

 

sw = Stopwatch.StartNew();

matchCount = 0;

for (int i = 0; i < maxIter; i++)

{

    if (src.Equals(dest, StringComparison.CurrentCultureIgnoreCase))

        matchCount++;

}

sw.Stop();

if (matchCount != maxIter)

    return;

Console.WriteLine("StringComparison.Ordinal CurrentCultureIgnoreCase " + sw.Elapsed.ToString());

 

sw = Stopwatch.StartNew();

matchCount = 0;

for (int i = 0; i < maxIter; i++)

{

    if (src.Equals(dest, StringComparison.OrdinalIgnoreCase))

        matchCount++;

}

 

sw.Stop();

if (matchCount != maxIter)

    return;

Console.WriteLine("StringComparison.Ordinal OrdinalIgnoreCase " + sw.Elapsed.ToString());

 

sw = Stopwatch.StartNew();

matchCount = 0;

for (int i = 0; i < maxIter; i++)

{

    if (string.Compare(src, dest, true) == 0)

        matchCount++;

}

sw.Stop();

if (matchCount != maxIter)

    return;

Console.WriteLine("string.Compare Ignore " + sw.Elapsed.ToString());

 

sw = Stopwatch.StartNew();

matchCount = 0;

for (int i = 0; i < maxIter; i++)

{

    if (string.CompareOrdinal(src.ToLower(), dest.ToLower()) == 0)

        matchCount++;

}

sw.Stop();

if (matchCount != maxIter)

    return;

Console.WriteLine("string.CompareOrdinal ToLower " + sw.Elapsed.ToString());

 

sw = Stopwatch.StartNew();

matchCount = 0;

for (int i = 0; i < maxIter; i++)

{

    if (string.CompareOrdinal(src.ToUpper(), dest.ToUpper()) == 0)

        matchCount++;

}

sw.Stop();

if (matchCount != maxIter)

    return;

Console.WriteLine("string.CompareOrdinal ToUpper " + sw.Elapsed.ToString());

 

 

 

결과

 

string.CompareOrdinal ToUpper 00:00:00.0020907
Upper Equal 00:00:00.0016855
StringComparison.Ordinal InvariantCultureIgnoreCase 00:00:00.0011733
StringComparison.Ordinal CurrentCultureIgnoreCase 00:00:00.0017487
StringComparison.Ordinal OrdinalIgnoreCase 00:00:00.0002009
string.Compare Ignore 00:00:00.0017312
string.CompareOrdinal ToLower 00:00:00.0017755
string.CompareOrdinal ToUpper 00:00:00.0020907 

 

+ Recent posts