2010년 12월 10일 금요일

Application의 MainWindow, Startup, ShutdownMode 프로퍼티




  Visual Studio의 WPF 응용프로그램 템플릿으로 프로젝트를 생성하면  App.xaml 과 MainWindow.xaml 파일이 생성된다.






App.xaml 파일의 Build Action 프로퍼티값이 ApplicationDefinition으로 설정되어 있기 때문에 WPF는 빌드 작업을 통해 App.xaml 의 클래스가 응용프로그램 클래스인 지 알아내고, Main() 함수를 생성하고 App의 클래스 인스턴스를 생성하며 Run()메소드를 호출한다. 일반적으로 반복되는 작업이므로 템플릿으로 자동으로 코드가 추가되는 것이다. 



 이 상태에서 빌드 및 실행하면 MainWindow가 출력이 되는데, 내가 원하는 것은 MainWindow가 출력되기 전에 다른 윈도우를 출력 시키고, 해당 윈도우의 리턴값에 따라 응용프로그램이 종료되거나 MainWindow를 출력시키는 것이다. 

 WinForm에서는 Program.cs 파일에서 이런 작업을 해주었는데 WPF에서는 어디서 해줘야 할까??



 WPF가 자동으로 Main()함수를 만들고, Application 인스턴스를 만든다. 또한 Application 인스턴스의  Run() 메소드를 실행 시킨다. Run() 메소드를 실행 시키면 Startup 이벤트가 발생하며 OnStartup 이벤트 핸들러가 호출되게 된다. 따라서 OnStartup 이벤트 핸들러에서 메인 윈도우를 출력하게 된다. 그래!! 그렇다면 여기서 MainWindow 호출 전에 처리할 작업을 작성하면 되는 것이다.








StartupUri 어트리뷰트를 지우고, Startup 어트리뷰트를 지정한다. 즉, Startup 이벤트의 이벤트 핸들러를 등록한다.








Startup 이벤트 핸들러에서 원하는 작업을 넣어주면 된다. MainWindow 를 사용하기 전에 WndDbLogin 윈도우를 먼저 사용하였으며 그 리턴값에 따라 MainWindow의 출력 유무가 결정된다.





Remarks



몇가지 주의해야 할 점이 있다. 



Application 응용프로그램 인스턴스는 제일 처음으로 생성되는  윈도우를 MainWindow 프로퍼티에 설정한다. 

즉, 최상위 윈도우로 설정하게 되는데 위 코드에서 WndDbLogin 을 먼저 생성하게 되면 이 윈도우가 MainWindow가 되므로 최상위 윈도우로 사용할 윈도우를 먼저 생성 및 MainWindow 프로퍼티에 설정해주다.



 응용프로그램은 보통 하나의 주 윈도우를 사용하며 주 윈도우가 종료되면 응용프로그램도 종료되게 된다. 

응용프로그램의 종료 모드는 ShutdownMode 프로퍼티를 통해 설정할 수 있으며 기본값으로는 OnLastWindowClose 열거값으로 설정되어 있다. 이 값을 OnMainWindowClose 열거값으로 설정해주자.

2010년 12월 3일 금요일

Business Form 을 위한 WPF StackPanel 의 사용




 Business logic 을 담은 Business Form WPF 응용프로그램에서 Layout 을 Grid 컨트롤로 많이 작성하게 된다. Grid 컨트롤은 매우 유연하여 많이 사용되어 지는데, 언제나 그렇듯 모든 곳에서 Grid가 적절한 것은 아니다. 

다음 블로그의 글을 참조하세요!!











요약하자면!!

Business Form 을 위한 Layout에서 StackPanel과 Grid의 사용은 각각 장점과 단점이 있다.



Grid 의 장점과 단점

- 유연한 Layout 구현

- Business Form 의 크기 변경 시, 내부 엘리먼트도 자동으로 re-sizing 된다. 특히 TextBox를 사용할 때 유용

- Grid는 row와 column에 번호를 매겨서 관리되기 때문에 기존 row/column 을 조작한다거나, 새로운 row/column을

   추가할 때 번호를 손 수 매겨야 하는 번거러움이 있다.



StackPanel 의 장점과 단점

- Grid만큼 유연하지 않지만(원래 StackPanel은 작게 구분된 영역을 정렬하는데 유용하다.)  Style을 설정함으로써 Grid와

  비슷한 유연함을 가질 수 있다.

- 기존 UI 엘리먼트의 변경, 새로운 엘리먼트 추가 시 번호를 매기는 번거로움이 없다.





Business Form 에서 re-sizing을 유념해야 한다면 Grid가 적절하지만, 고정된 크기를 갖는 Business Form 에서는 StackPanel 역시 유용할 수 있는 내용이다.



어차피 어떤것을 구현함에 있어 방법은 많다. 이렇게도 해보고, 저렇게도 해보고, 이렇게 하면 이게 좋고, 저렇게 하면 저게 나쁘구나.. 이런 많은 경험이 있어야 되겠다.













2010년 12월 2일 목요일

Creating Border-less Windows in WPF











Border-less Windows 즉, 윈도우 창에 테두리가 없는 윈도우를 말한다. WPF 에서 스플래쉬 윈도우나, 일반적이지 않은 특별한 윈도우를 만들기 위해서는 border 가 없는 윈도우에 작업을 한다. 비단 WPF 뿐만이 아니라 MFC, WinForm 에서도 이런 방식을 사용하고 있다. 






Figure 1 : border 를 갖지 않는 윈도우






Setting Window Attributes



Border가 없는 윈도우를 만들기 위해서는 window의 다음 어트리뷰트를 설정해주어야 한다.



● WindowStyle="None"

● ShowInTaskBar="False"

● AllowsTransparency="True"

● Background="Transparent"



WindowStyle, ShowInTaskBar 어트리뷰트 WinForm 에서도 사용되어지니 눈에 익숙하다. 실제로 border를 없애는 것은 WindowStyle 어트리뷰트이며 ShowInTaskBar는 선택적인 옵션이다. AllowTransparency 과 Background 어트리뷰트가 중요하다. 이 두 어트리뷰트는 함께 설정해야 하며, Background를 Transparent로 설정하기 위해선 AllowTransparency를 True로 설정해야 한다. 이 두 어트리뷰트를 설정하지 않으면 border가 계속 보이게 된다.






Figure 2 : WindowStyle="None", AllowTransparency 와 Background 는 설정하지 않은 경우





예제 XAML 코드



<Window x:Class="NoBorderDemo.MainWindow"

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="MainWindow" 

        Height="350" Width="525"

        WindowStyle="None" AllowsTransparency="True" Background="Transparent"

        ResizeMode="NoResize"

        ShowInTaskbar="False"

        WindowStartupLocation="CenterScreen">

    <Border BorderBrush="Gray"

            BorderThickness="0, 0, 2, 2"

            CornerRadius="10"

            Background="Beige">

        <Border BorderBrush="Transparent"

                BorderThickness="5"

                CornerRadius="10">

            <Border BorderBrush="Black"

                    BorderThickness="1.5"

                    CornerRadius="10">

                <StackPanel Background="Yellow"

                            VerticalAlignment="Bottom"

                            HorizontalAlignment="Center"

                            Margin="10">

                    <Button Click="Button_Click">

                        Close Me

                    </Button>                    

                </StackPanel>

            </Border>        

        </Border>

    </Border>

</Window>





2010년 11월 28일 일요일

C# Convert Int to Bool












 다른 모듈과 설정파일을 공유하는 경우가 있다. 한 예로 MFC 모듈과 .NET 모듈이 XML 파일로 설정을 공유하는데, 특정 기능의 사용, 사용 안함을 1 과 0 으로 정의하여 사용하고 있다. 설정을 읽고 체크박스 컨트롤의 상태를 결정할 때 다음과 같은 코드를 주로 사용하였다.





int useUpdate = [xml파일로 부터 설정 읽기];


ckbUseUpdate.Checked = useUpdate == 1;





 몰랐는데 int 값을 bool값으로 변환해주는 FCL이 있었다. 바로 Convert.ToBoolean


매개변수로 int 값을 받을 때, 0일 경우 false 그 외에 경우에는 모두 true 를 리턴한다.










 코딩할 때, 어쩔 수 없는 경우라면 직접 만들어야 하지만 가능하면 FCL을 이용하려 한다.





But,

프로그래밍 잘 한다고 생각하는 친구로 부터 조언을 받았다. 항상 고맙게 생각하는 친구다.

어느 언어를 막론하고 형 변환은 조심스럽게 사용해야 한다. 수 형들간의 형변환 시, Convert의 정적 메소드, 데이터 타입의 Parse, TryParse 정적 메소드를 사용하게 된다. 이 중 형 변환 시 예외를 발생시키지 않는 것은 TryParse 이다. try-catch 문을 과도하게 쓰는 코드는 좋지 않다. 따라서 TryParse 메소드의 사용이 유리한 점이 많다.




2010년 11월 27일 토요일

Create a Login Window in WPF



출처 : Create a Login Window in WPF - Paul Sheriff's Blog










Create a Login Window in WPF

Most business applications require some sort of security system. You can always use Windows Authentication to authenticate a user, but sometimes you might want your own authentication scheme. When you do, you will need to create a login screen for your user to enter her login id and password. This article will explore creating a login window in WPF.

The UI for your login screen can contain any images and controls that you would like. In Figure 1 you can see a sample login screen that has an image of a key, a large title label across the top, two labels, a text box, a password box and two button controls. You can also make the border of the window rounded so that there is no close, minimize or maximize button and no title bar.

Figure 1: A Login Screen

Of course this is just one version of a login screen but let’s take a look at how this is put together.

Creating the Window

To start, you need to create a window with no border and can be made into any shape you want. To do this you will set a few different attributes. The WindowStyle attribute normally allows you to set a single border, three-D border, or a Tool Window border. Setting this attribute to None will eliminate the border. The ShowInTaskbar attribute is optional, but if you are building a login screen you probably won’t want this window to show up in the Task Bar as it is going to be modal style form. The next two attributes, AllowsTransparency and Background work together. You must set AllowsTransparency to True to allow the Background to be set to Transparent. If you do not set these two attributes, then your border will still show up. Below is the xaml for this window.

<Window ...
   WindowStartupLocation="CenterScreen"
   AllowsTransparency="True"
   ShowInTaskBar=False
   Background="Transparent"
   WindowStyle="None"
   SizeToContent="WidthAndHeight"
   FocusManager.FocusedElement=
          "{Binding ElementName=txtUserName}">

   ...
   ...

</Window>

There are three additional attributes that are set on this window. The WindowStartupLocation attribute is set to “CenterScreen”  to ensure that the login screen is displayed in the middle of the screen when it is shown. You also set the SizeToContent attribute to WidthAndHeight to just take as much room for this window as the controls need that are contained within this window. The FocusManager.FocusedElement attribute is data-bound to the textbox control next to the User Name label. This tells WPF to place the cursor in this textbox once the screen is displayed.

The Border

Now that you have the Window xaml defined you now can create the look for the outside border of the window. A Border control is used to form the outside of this login screen. You will set the CornerRadius attribute to “10” to give the nice rounded corners. You can set the BorderBrush to “Gray” and the BorderThickness to “3”. You also want to give this border a nice wide Margin to allow room for the DropShadowEffect that we add to the outside of this border. If you do not do this, then the drop shadow will be chopped off. To achieve the shadow effect on this window, you will use the new Border.Effect and the DropShadowEffect that was added in WPF 3.5. This drop shadow effect is drawn using the Graphical Processing Unit (GPU) instead of being drawn using software. Thus drawing drop shadows is much more performant than in previous versions of WPF.

<Border CornerRadius="10"
        BorderBrush="Gray"
        BorderThickness="3"
        Background="Beige"
        Margin="24"
        Padding="4">
  <Border.Effect>
    <DropShadowEffect Color="Gray"
                      Opacity=".50"
                      ShadowDepth="16" />
  </Border.Effect>

   ...
   ...

</Border>

Using a Grid Layout

To place each of the login screen elements within the border, a Grid control is used with specific column and row definitions. There are three columns in this login screen. One for the image of the key, one for the labels and one for the TextBox, PasswordBox and Button controls.

<Grid>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="60" />
    <ColumnDefinition Width="100" />
    <ColumnDefinition Width="*" />
  </Grid.ColumnDefinitions>
  <Grid.RowDefinitions>
    <RowDefinition />
    <RowDefinition />
    <RowDefinition />
    <RowDefinition />
  </Grid.RowDefinitions>

   ...
   ...

</Grid>

Placing the Key Image

The Key image that is in the upper left hand corner of this login screen is placed there by using a StackPanel control and an Image control. The StackPanel gives us just a little more control over the placement within the Grid. Notice the Grid.Column, Grid.Row and Grid.RowSpan attributes that are set on the StackPanel. The Grid.Row and Grid.Column specify in which row and column of the grid you wish to display the StackPanel. The Grid.RowSpan allows the key to float down over the next three rows of the Grid control. If you were to use a smaller or larger key image, then you would probably need to adjust this attribute accordingly. The Image control sets the source of its image to the Key.jpg file located in the /Images folder. A drop shadow effect is applied to this image control just like you did with the Border control.

<StackPanel Grid.Column="0"
            Grid.Row="0"
            Grid.RowSpan="3">
  <Image Name="imgKey"
         Margin="8"
         Source="/Images/Key.jpg">
    <Image.Effect>
      <DropShadowEffect Color="Gray"
                        Opacity=".50"
                        ShadowDepth="8" />
    </Image.Effect>
  </Image>
</StackPanel>

The Large Title Label

The large label across the top of the login screen is simply a Label control with the appropriate Grid.Row, Grid.Column and Grid.ColumnSpan attributes set for placement. A FontSize of 18 is applied to make the text appear larger than the other labels on this screen. A Margin of 10 is used to give us some spacing from the border of the grid.

<Label Grid.Column="1"
       Grid.Row="0"
       Grid.ColumnSpan="2"
       FontSize="18"
       Margin="10">Please Login To Access This Application
</Label>

The Login Data Controls

The controls that gather the user name and password should be fairly familiar to you if you have been doing any WPF at all. Each control is placed into a specific row and column of the Grid control. Notice the use of the Tooltip attribute on the TextBox and the PasswordBox control. This gives the user an idea of what to put into each control if they hover their mouse over that control.

<Label Grid.Column="1"
       Grid.Row="1">User Name</Label>
<TextBox Grid.Column="2"
         Grid.Row="1"
         ToolTip="Enter Your User Name"
         Name="txtUserName" />
<Label Grid.Column="1"
       Grid.Row="2">Password</Label>
<PasswordBox Grid.Column="2"
             Grid.Row="2"
             ToolTip="Enter Your Password"
             Name="txtPassword" />

The Buttons

The two buttons at the bottom of the screen are placed into the last row of the Grid control and into the second column of the grid by wrapping them into a StackPanel. The StackPanel has its HorizontalAlignment attribute set to Center and it’s Orientation attribute to Horizontal to allow the buttons to be centered within the StackPanel and to have the buttons appear side-by-side to each other.

<StackPanel Grid.Column="2"
            Grid.Row="3"
            Margin="10"
            HorizontalAlignment="Center"
            Orientation="Horizontal">
  <Button Name="btnCancel"
          IsCancel="True"
          Content="Cancel"
          Click="btnCancel_Click">
    <Button.Effect>
      <DropShadowEffect Color="Gray"
                        Opacity=".50"
                        ShadowDepth="8" />
    </Button.Effect>
  </Button>
  <Button Name="btnLogin"
          IsDefault="True"
          Content="Login"
          Click="btnLogin_Click">
    <Button.Effect>
      <DropShadowEffect Color="Gray"
                        Opacity=".50"
                        ShadowDepth="8" />
    </Button.Effect>
  </Button>
</StackPanel>

There are two special attributes that are set on these buttons. The IsCancel attribute is set to true on the Cancel button. Setting this attribute to true will fire the click event procedure on the Cancel button if the user presses the Escape key. The IsDefault attribute is set to true on the on the Login button. Setting this attribute to true will fire the click event procedure on the Login button if the user presses the Enter key.

Writing the Code for the Login Screen

In each of the click event procedures you will need to close the screen. In the Cancel click event procedure you will set the DialogResult property of the screen to a false value. This will inform the calling procedure that the user clicked on the Cancel button on this screen. In the Login click event procedure you will set the DialogResult property of the screen to a true value. This informs the calling procedure that the user clicked on the Login button and was authenticated. I am leaving it up to you to write the code for authenticating the user. Here is the code for the Cancel event procedure.

C#private void btnCancel_Click(object sender, RoutedEventArgs e)
{
  DialogResult = false;
  this.Close();
}

Visual BasicPrivate Sub btnCancel_Click(ByVal sender As System.Object, _
 ByVal e As System.Windows.RoutedEventArgs)
  DialogResult = False
End Sub

And, here is the code for the Login event procedure.

C#private void btnLogin_Click(object sender, RoutedEventArgs e)
{
  // Write code here to authenticate user
  // If authenticated, then set DialogResult=true
  DialogResult = true;
}

Visual BasicPrivate Sub btnLogin_Click(ByVal sender As System.Object, _
  ByVal e As System.Windows.RoutedEventArgs)
  DialogResult = True
End Sub

Displaying the Login Screen

At some point when your application launches, you will need to display your login screen modally. Below is the code that you would call to display the login form (named frmLogin in my sample application). This code is called from the main application form, and thus the owner of the login screen is set to “this”. You then call the ShowDialog method on the login screen to have this form displayed modally. After the user clicks on one of the two buttons you need to check to see what the DialogResult property was set to. The DialogResult property is a nullable type and thus you first need to check to see if the value has been set.

C#private void DisplayLoginScreen()
{
  frmLogin frm = new frmLogin();

  frm.Owner = this;
  frm.ShowDialog();
  if (frm.DialogResult.HasValue && frm.DialogResult.Value)
    MessageBox.Show("User Logged In");
  else
    this.Close();
}

Visual BasicPrivate Sub DisplayLoginScreen()
  Dim frm As New frmLogin()

  frm.Owner = Me
  frm.ShowDialog()
  If frm.DialogResult.HasValue And frm.DialogResult.Value Then
    MessageBox.Show("User Logged In")
  Else
    Me.Close()
  End If
End Sub

Summary

Creating a nice looking login screen is fairly simple to do in WPF. Using the DropShadowEffect can add a nice finished look to not only your form, but images and buttons as well. Using a border-less window is a great way to give a custom look to a login screen or splash screen. The DialogResult property on WPF Windows allows you to communicate back to the calling routine what happened on the modal screen. I hope this article gave you some ideas on how to create a login screen in WPF.



2010년 11월 20일 토요일

[Coding Style] Using C# predefined types or alias ?

C# Coding Standard by IDesign 문서를 읽고 있다. 읽다 보니까 다음과 같은 지침이 있다.

"Always use C# predefined types rather than the aliases in the System namespace"



자주 사용하는 string 타입을 예로 들면,

C# predefined type : System.String
Alias                    : string


"string" 은 System.String 의 Alias 즉 별칭이다. 예전에 읽었던 문서에서는 해당 타입의 alias가 있으면 alias를 사용하라고 했었는데 지금 읽는 문서에서는 또 그러길 비추 하고 있다. 


 딱히 어떤것이 더 유리한지 떠오르지 않아 검색해 보았다. 검색해 보니까 나와 똑같은 고민을한 흔적들이 보인다.


 자료를 읽던 중 신기한 현상을 발견했다. enum 타입을 정의할 때 요소의 데이터 타입을 지정해 줄 수 있다. 데이터 타입을 명시할 때 데이터의 전체이름(C# predefined type)을 지정해 주면 컴파일이 되지 않는다. 




또한 C# 3.5에 추가된 익명타입 var 의 사용에서도 제한이 따른다.




 어차피 코딩 스타일이라는게 개인마다 정하기 나름이지만 대부분의 경우에 있어서 alias를 사용하는것 같다. alias를 사용하면 코드의 가독성이 높아진다.  단, 메소드 이름에 데이터 타입이 들어갈때는 alias 보다는 predefined type 이름을 사용하는게 더 명확하다.



2010년 10월 27일 수요일

[InstallShield 2008] OCX 파일 수동 등록













  확장자가 ocx 인 파일을 사용하는 어플리케이션은 해당 어플리케이션을 설치할 때 ocx 파일을 시스템에 등록해주어야 한다. 이 작업이 일반적인 작업이라 InstallShield 에서 설정값만 변경하면 알아서 시스템에 등록해준다. 하지만 여러 언어의 OS에 설치해본 결과 등록될 때도, 등록되지 않을 때도 있다. 음 어떤 설정의 문제인지는 모르겠지만..








1. InstallShield 에서 OCX 파일 등록 설정










InstallShield 에서 Installation Designer → Organization → Components 로 등록할 경우, 해당 Component 프로퍼티에서 Self-Register 값을 Yes 로 하면 된다. 그러면 InstallShield가 어플리케이션을 설치하면서 해당 모듈을 자동으로 등록해 준다. 








2. 스크립트를 이용한 OCX 파일 수동 등록



 위와 같이 자동으로 등록하게 했는데 이상하게 등록이 안되는 경우가 있었다... 모든 경우를 대비하여 수동으로 등록하는 스크립트를 추가해 줬다. 











function OnEnd()


    string szProgram, szCmdLine;


begin


    szProgram = WINSYSDIR ^ "regsvr32.exe ";


    szCmdLine = TARGETDIR ^ "파일이름";


    //LongPathToShortPath (szCmdLine);


    LongPathToQuote (szCmdLine, TRUE);


    MessageBox(szCmdLine, INFORMATION);


    LaunchAppAndWait(szProgram, szCmdLine, WAIT/*LAAW_OPTION_CHANGEDIRECTORY |  LAAW_OPTION_FIXUP_PROGRAM /*LAAW_OPTION_WAIT*/); 


end;









 Windows 에서 ocx를 등록할때 regsvr32.exe 를 이용한다. 즉 Windows 실행창에서 다음과 같이 입력하여 수동으로 등록한다.


regsvr32 MyComponent.ocx


regsvr32.exe 는 Windows system 폴더에 있으므로 다음과 같이 전체 경로를 구해준다.


WINSYSDIR ^ "regsvr32.exe"; 





해당 경로로 실행시켜 주면 된다.





TARGETDIR 이 보통 "C:\Program Files" 하위에 위치하게되는데 이 값을 그냥 전달하게 되면 "C:\Program" 과 그 나머지 부분으로 짤려서 전달하게 되므로 원하는 대로 동작하지 않는다.  위의 스크립트에서 LongPathToQuote() 메소드가 이 부분을 보완해주는 함수 같긴 한데.. 아래와 같은 방법으로도 문자열이 짤리는 것을 막을 수 있다.






szOcx = "\"" + TARGETDIR ^ "MyComponent.ocx" + "\"";





2010년 10월 26일 화요일

XML 특수 문자 및 예약어 사용



 간단한 XML 작업에 대해서 큰 불편함이 없지만 특수 문자가 들어가는 문자열을 사용할 때 걸리는게 있다. 대표적인 것 몇가지만 정리해 두자.


1. C/C++ 계열 언어에서의 개행 문자 \n

\n → &#10;

ex). <data="오류 내용 \n 잘못된 입력" /> → <data="오류 내용 &#10; 잘못된 입력" />



※ 특수 문자를 입력하는 방법


&#ASCII;




2. 예약 문자 입력

< → &lt;

> → &gt;

& → &amp;








2010년 10월 24일 일요일

[InstallShield 2008] 다국어 OS 다이얼로그 크기





 InstallShield 2008로 다국어 설치본을 제작하고 있다. 영문 String Table 및 다이얼로그 설정까지 마친 후, 실제 영문 OS에 설치해 보니 대화상자가 이상하게 크게 나타난다. 한글 OS에서는 정상적으로 보이는데...








 우선 정확한 이유는 모르겠지만 영문 다이얼로그 디자인에서 TextStyle을 적절히(?) 설정해주지 않아서 생기는 문제 같다.















Installer Designer 에서 User Interface 트리를 보자. 하위 트리에 Dialogs를 선택한 후 우측으로 사용하는 InstallShield 다이얼로그 목록이 보인다. 사용하는 다이얼로그는 굵은 글씨로 표현된다. 해당 프로젝트가 지원하는 언어에 따라 Korean, English 등이 하위 트리로 추가 된다.













해당 다이얼로그를 선택하면 우측으로 프로퍼티 목록이 보인다. 여기서 Text Style 을 보면 기본적으로 New_TextStyle로 설정되어 있다. 물론.. 이 설정은 InstallShield의 버전 및 설치된 OS 언어에 따라 다를수도 있겠다.











New_TextStyle 일 경우 한글 OS에선 잘 보이지만 영문 OS에서는 위의 경우처럼 다이얼로그가 대빵만하게 보인다. Text Style 프로퍼티에 MSSansSerif8 로 설정해 주니 영문 OS에서도 깔끔하게 다이얼로그가 출력된다.










2010년 10월 21일 목요일

[InstallShield 2008] 설치될 OS의 언어에 맞는 언어 선택









  제품이 설치될 OS의 언어가 한가지라면 다국어 작업을 해야 한다.





언어 리소스의 추가는 General Information 의 Project Properties 에서 해주면 된다. Setup Language 프로퍼티에서 국가를 추가해 주면 된다.































 설치본(Media)의 언어 선택은 어떻게 해야 할까??


Installation Designer 의 Media - Release 에서 선택해 준다. SINGLE_EXE_IMAGE 에서 Language(s), Default Language, Languages Dialog 프로퍼티 속성을 잘 정의해 준다. 여기서 집고 넘어갈 것은 Language Dialog 프로퍼티 속성을 false 로 한다면 설치본은 해당 OS의 언어로 설정되어 설치된다. 설치본에서 지원하는 언어가 없을 경우 Default Language 의 설치본으로 설치가 된다.











































2010년 10월 8일 금요일

Exception 처리



 Effective C# 에 보면 "ITEM #44. 애플리케이션에 특화된 예외 클래스를 완벽하게 작성하라." 라는 조언?! 이 있다. 아쉽게도 일하는곳에서는 배울만한 것이 아무것도 없기에 답답해 하다가 Head First C# 을 보고 Exception 처리에 대한 기본이라 생각해 정리해 본다.








 "하나의 클래스가 예외를 발생시키면 또 다른 클래스는 예외를 잡아냅니다."





 예외를 발생시킨다는 사실 뒤에 숨어있는 핵심 요지는 무엇이 잘못될 것인가 하는 것이며, 따라서 이에 대한 비상 계획을 수립할수 있다. 일반적으로는 예외를 발생 시키고 동시에 그것을 잡아내는 메소드는 그리 흔하지 않다. 하나의 메소드에서 예외를 발생시키면 다른 객체의 또 다른 메소드에서 잡아내는 경우가 대부분이다.










즉, 위와같이 Hive 객체가 BeeProfile 객체를 생성 또는 BeeProfile의 메소드를 호출 하였을 때,  BeeProfile 에서 예외가 발생하면 BeeProfile 객체 내부에서 try-catch 로 자신의 예외를 잡아서 오로지 true, false 로 결과만 리턴해 주는게 아니다. BeeProfile 객체는 예외를 발생시켜 구체적인 예외를 던지고, 피 호출자가 해당 예외를 잡아, 각각의 예외에 맞는 처리를 해주는 것이다.











 "예외 클래스 작성"





.NET 에서 발생하는 예외 클래스들의 최상위 클래스는 Exception 클래스 이다. 따라서 사용자가 정의할 예외는 Exception 클래스를 상속 받아 자신만의 예외 클래스를 작성하면 된다.










Excpetion 클래스를 상속받아 기본 생성자만을 사용할 수 있지만, 생성자에 메시지를 인자로 갖는 생성자를 사용하여 예외가 발생한 이유를 직접적으로 알릴 수 있도록 한다.









핵심 정리!!
1. 런타임 시, 실패하는 뭔가가 발생하면 어떤 문장이라도 예외를 발생 시킬 수 있다.

2. 다양한 종류의 예외가 있는데, 각각은 Exception에서 상속받은 객체를 갖고 있다. Exception이 아닌 특정한 예외를 잡아낸다.
:  다형성에 의해 Exception 으로 모든 예외를 다 받을 수 는 있지만 발생한 각각의 예외에 따른 정확한 처리를 위해 Exception 이 아닌 해당 예외 객체로 예외를 잡고 처리해야 한다.

3. 대부분은 ArgumentException 같은 .NET 이 제공하는 예외만 발생시켜도 된다. 다른 여러 종류의 예외를 사용하는 이유는 사용자에게 각각에 대한 정보를 제공해 주기 위해서 이다. "알려지지 않은 에러가 발생했습니다." 와 같은 텍스트를 보여주는 창을 띄우는 것 보다는 "선택한 폴더가 비어있습니다. 파일이 있는 다른 폴더를 선택하세요" 를 보여주는 것이 훨씬 도움이 될 것이다.










"역사상 최악의 catch 블록 : 주석"








위 예제에서 인자로 전달받은 divisor 가 0 이면 0 으로 나누는 식이 되기 때문에 DivideByZeroException 이 발생한다. 그렇다면 이 코드가 왜 최악의 catch 블록인걸까??



묻어두는 예외




위 코드에서 0 으로 나누는 경우가 발생할 경우 try-catch로 일단 예외를 잡았기 때문에 프로그램이 비정상 종료 되지는 않는다. 하지만 0으로 나누는 경우 발생하는 예외로 인한 적극적인 처리, 알림이 없기 때문에 런타임에는 도대체 quotient가 비정상적인 값이 나오는지 알 수가 없다. 비정상 적인 값이 나온다고 일일이 디버깅 할 수도 없는 노릇이고. 더 복잡한 코드에서 이런 경우가 발생하면 한참을 디버깅 할 것이다. 퇴근은 못하겠지.. 하지만 상사는 퇴근하겠지... 

협업으로 개발하는 코드를 보면 저런 코드가 상당히 많다. 내가 뭐라할 위치가 되지 않아 가만히 두고 있는데... 상당히 안타깝다...







"일시적인 해결책도 당분간은 쓸모가 있습니다."



 예외가 발생하였을 경우, 상황에 따라 개발자가 해결책을 유도하기 보다 사용자에게 오류의 원인을 알리고, 올바른 해결책의 정보를 제공해줘야 할 때가 있다. 또한 개발자도 예외 발생 시 메시지를 발생하게 하여 해당 메시지로 예외의 원인을 파악하게 할 수 있다. 예외를 처리하는 것만큼 좋은 것은 아니지만 아무것도 하지 않는 것보다는 낫다.






"프로그램이 산산히 부서지는 것은 절대 좋지 않지만, 프로그램이 왜 충돌하고 사용자의 데이터를 가지고 무슨 일을 하는지 알지 못하는 것이 더 나쁩니다. 언제나 예상할 수 있는 에러를 처리했는지 확인하고, 처리하지 못하는 것을 기록해야 하는 이유가 바로 여기 있습니다."









예외 처리를 위한 몇 가지 간단한 팁!!

1. 에러를 우아한 방식으로 처리하는 코드를 작성하세요.

2. 사용자에게 도움이 되는 에러 메시지를 보여주세요.

3. 가급적이면 내장 .NET 예외를 던지세요. 자신만의 정보를 제공해야 할 때문 사용자 정의 예외를 발생시키세요.

4.  try블록에서는 프로그램을 중단할 것 같은 코드를 작성하세요.

5. 불필요한 파일 시스템 에러는 피하세요. 스트림을 사용할 때는 항상 using 블록을 사용하세요.









cf) Head First C#

2010년 10월 5일 화요일

[InstallShield 2008] Uninstall 시 파일 삭제 결정









 기본적으로 InstallShield로 설치된 파일들은 Uninstall 시 같이 삭제 된다. 처음엔 별다른 설정 없이 이 기능이 참 맘에 들었다. 하지만 설치본도 복잡해 지면서 다른 모듈과의 연관성에 의해 삭제하면 안되는 파일이 있었다. 





 InstallShield2008에서는 Installation Designer 에서 프로퍼티를 설정하는 방법으로 쉽게 설정할 수 있다.










Installation Designer 에서 왼쪽에 보이는 Organization 트리를 펼쳐준다. Setup Design 또는 Components를 선택하면 우측으로 설치하려는 Component들의 트리 구조를 볼 수 있다. 여기서 해당 Component를 마우스로 선택하면 아래와 같이 Property를 설정할 수 있는 화면이 보여진다.













Uninstall 프로퍼티는 Uninstall 시, 해당 Component를 삭제할 것인지를 나타내는데, 기본이 Yes로 되어 있어서 Uninstall 시 파일들이 삭제되었던 것이다. 이 값을 No로 설정하면 Uninstall 시 자동으로 삭제되지 않는다.











2010년 10월 4일 월요일

Howto : Detect OS Architecture with C#

출처 : Howto : Detect OS Architecture with C#







I’ve had a problem in one of the utility applications/scripts I’ve been developing, I had to find out the Operating System Architecture, where application was running, here is the process I’ve used to do so:



   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Text;
   4:   
   5:  namespace SystemUtilities
   6:  {
   7:   // Enum to describe OS Architecture.
   8:   public enum OSArchitecture
   9:   {
  10:     X84,
  11:     X64
  12:   }
  13:   // Class to retrieve specific OS Variables. 
  14:   // And detect specific enviroiment conditions.
  15:   public class OSUtilities
  16:   {
  17:     // Function returns type of OS Architecture.
  18:     public OSArchitecture GetOSArchitecture()
  19:     {
  20:      if ( Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") == "AMD64" 
  21:        || Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE") == "IA64"
  22:        || Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432") == "AMD64"
  23:        || Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432") == "IA64" )
  24:      { return OSArchitecture.X64; }
  25:   
  26:      return OSArchitecture.X84;
  27:     }
  28:   }
  29:  }


















cf) 참고





 OS Architecture 정보 즉 x86, x64 인지에 대한 정보는 레지스트리 HKLM\System\CurrentControlSet\Control\Session Manager\Environment 키의 PROCESSOR_ARCHITECTURE 값을 조사해도 알 수 있다. 또한 BCL System.Environment 정적 클래스를 이용하여 구할 수 도 있다. Environment.GetEnvironmentVariable 메소드를 이용하여 구할 수 있는데 약간 주의를 해주어야 한다. PROCESSOR_ARCHITECTURE 환경 변수값을 조사하면 OS Architecture 가 x86인 경우 "x86" 문자열 값을 구할 수 있지만 OS Architecture 가 x64 인 경우 구해지는 값은 "AMD64" 또는 "IA64" 값을 얻는다. 따라서 PROCESS_ARCHITECTURE 값이 "AMD64", "IA64" 인 경우 OS ARCHITECTURE가 x64이다. 


 Environment.GetEnvironmentVariable 의 두번째 매개변수로 타겟을 지정해 줄 수 있는데, 이 매개변수를 지정해 주지 않으면 실행시킨 프로세서를 기준으로 값이 얻어지므로 EnvironmentVariableTarget.Machine 을 이용해 주자.











C# region 에 대하여.

 조금 여락한 환경에서 .NET 개발을 하고 있다. 조금은 더 효과적, 체계적으로 배우고 싶은데..




C# 코드 파일은 C 계열 언어와 달리 .cs 파일 하나에 코드를 구현한다. 구현에 있어 클래스가 길어 질 수 있는데(클래스가 장대하게 길어지면 그것도 디자인을 잘못한듯) #region-#endregion을 이용하여 코드를 문서화 할 수 있다. 딱 여기까지는 익히 아는건데 도데체 어떻게 써야 잘 썼다고 말할까?! 고민하다 Open Source 인 log4net 은 어떻게 사용하나 살펴보았다. 


정답은 없지만 그래도 Open Source 의 코드는 경험 많은 개발자들의 코드이므로 도움이 될 것 같다.





 보통 클래스 내 멤버의 접근 지정자에 따라 분류한다. 










1. public, protected, private 등 접근 지정자 별로 분류


2. 같은 접근 지정자일 경우, 생성자, 프로퍼티, 필드 등 으로 분류


3. 가시성은 public 멤버를 제일 상단에 배치













또는 인터페이스의 구현, helper, static wrapper 와 같이 성격이 비슷한 것 끼리 묶을 수 도 있다.













#endregion 라인에 아무것도 써주지 않아도 되는데, #region-#endregion 부분이 길어질 경우 #endregion 만 써 놓으면 어떤 부분인지 찾아야 되는 번거러움이 있으므로 #endregion 옆에도 참고가 될 설명을 붙여준다.













인터페이스 구현 시 IDE가 #region 을 자동으로 추가해 준다. 구현하는 인터페이스 별로 분류하니 보기도 좋다.








 첨엔 #region-#endregion 으로 분류 해 놓으니 개발하는 중간에는 파일을 열 때 마다 #region 을 풀어주는게 귀찮았던 적이 있다. 귀찮아서 하지 않은 코드가 있었는데 나중에 열어보니... 어후...  나중을 위해 서라도 #region-#endregion 잘 활용해 보자

모를땐, 익숙하지 않을땐 따라하면서 배워야지!!



2010년 9월 29일 수요일

[InstallShield 2008] 레지스트리 키 기록











 프로그램 설치 시, 레지스트리를 등록해야 하는데, 스크립트에서 호출하는 DLL에서 해줄까 하다가.. 간단한 거는 그냥 InstallShield 스크립트로 기록하고자 했다.





 InstallShield 2008 마법사(?!)를 최대한 활용하고자 했는데.. 내가 넘겨 받은 설치본은 무작정 스크립트로 때려 박은 코드였다. 아... 언제나 느끼는 거지만 대충 만들어서 나한테 떠넘길때 정말 짜증.. 넘길꺼면 좀 제대로 만들기나 하지!!







레지스트리 등록





스크립트로도 할 것이지만 Installation Designer 에서 레지스트리 키를 등록. 여기서 작업할 때 장점은, 레지스트리의 등록을 구조적으로 쉽게 보여주고, Uninstall 시 같이 삭제할 것인지도 설정할 수 있다.













스크립트 작성





레지스트리 키 및 값을 등록하는 함수를 작성했다. 




function CreateRegInstallDir()


STRING szKey; // 레지스트리 키 경로


STRING szAppKey; // App 레지스트리 키 경로


STRING szApp; // App 이름


STRING szInstallDir;        // InstallDir 값 이름


begin                                      


REGDB_OPTIONS = REGDB_OPTIONS | REGDB_OPTION_WOW64_64KEY;


RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE);          




szKey = "Software\\COMPANY\\AA\\BB";                     


szAppKey = "Software\\COMPANY\\AA\\BB\\Demo";


szApp = "Demo";                        


szInstallDir = "InstallDir";  




if (0 < RegDBKeyExist (szAppKey)) then 


RegDBCreateKeyEx(szKey, szApp);


endif;          




RegDBSetKeyValueEx(szAppKey, szInstallDir, REGDB_STRING, TARGETDIR, -1);  


REGDB_OPTIONS = REGDB_OPTIONS & ~REGDB_OPTION_WOW64_64KEY;


end; 










RegDBKeyExist() 함수를 이용하여 레지스트리 키가 있는지 검사한다. 없으면 RegDBCreateKeyEx() 함수를 이용하여 키를 생성한다. 값을 등록할때는 RegDBSetKeyValueEx() 함수를 이용한다. 마지막 파라미터로 기록할 값의 길이를 지정해 줘야 하는데 -1을 넘겨주면 알아서 계산해 준다. -1 파라미터를 사용할 수 있는 레지스트리 값 타입이 따로 있으니 조심.





각 함수는 도움말을 참조 하면 자세히 알 수 있다.















레지스트리 삭제



szKey = "Software\\App\\Data";


RegDBSetDefaultRoot(HKEY_LOCAL_MACHINE);         


if (1 == RegDBKeyExist (szKey)) then


RegDBDeleteKey (szKey); 


endif;

2010년 9월 28일 화요일

클래스 라이브러리에서 응용프로그램 실행 위치 구하기




 WinForm 응용프로그램의 실행 파일 경로는 System.Windows.Forms 네임스페이스에 있는 Application.StartupPath 프로퍼티를 참조하면 쉽게 구할 수 있다.


using System.Windows.Forms;

...

string path = Application.StartupPath;


 클래스 라이브러리가 WinForm 모듈이 아닐 경우에는 System.Reflection 네임스페이스에 있는 Assembly 클래스를 이용하면 된다. Assembly.GetEntryAssembly().Location 프로퍼티를 이용하면 실행 파일 이름을 포함한 전체 경로를 얻게 되므로 Path 클래스를 이용하면 쉽게 구할 수 있다.



using System.Reflection;

...

string path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);



 Assembly.GetEntryAssembly().Location MSDN 설명을 보면 클래스 라이브러리 즉, 관리되는 코드를 관리되지 않는 코드에서 호출할 경우 null을 리턴한다고 나와있다. 이것만 주의해 주면 되겠다.



2010년 9월 26일 일요일

생성자 내부에서 발생한 예외 처리







생성자 내부에서 발생한 예외 처리








 Head First C# 책을 읽고 있는데 다음과 같은 글이 쓰여 있다.





"여기 유용한 팁이


있군요. C# 프로그래머


면접 시 생성자 내부에서


발생한 예외를


어떻게 처리할 것인지 묻는 


경우가 많죠."





음... 갑자기 정신이 바짝!! 든다.  MFC 프로그래머 면접때는 Main() 함수의 위치에 대해 물어보곤 했었는데. 후후.





생성자는 객체를 초기화 하기 위한 목적이며, 생성자가 성공적으로 호출되면 객체의 인스턴스가 생성된다. 생성자에서 예외가 발생하였다면 객체의 인스턴스를 완료하지 못한것이다. 즉, 생성자 내부에서 예외가 발생한다면 그 코드는 정상적인 객체를 생성하지 못하였으므로 코드를 다음부분으로 진행해서는 안된다. 예외가 발생할 수 있는 생성자를 가진 객체를 생성할 때, try-catch 블록으로 감싸주어 예외에 맞는 처리를 해주어야 한다.





다음 링크를 참조하면 더 쉽게 이해할 수 있다.




2010년 9월 12일 일요일

Attribute [Category], [Description]

Attribute [Category], [Description]







 아직까지 어트리뷰트를 많이 사용하지 않았다. 그 필요성을 느끼는 단계까지 나의 내공이 부족한 걸까? 아니면 환경이??

모... 불평하거나 위측하지 않으련다.



 CodeProject 에서 자료를 찾다가 남의 소스를 봤는데 희한한게 있었다. Category, Description 어트리뷰트가 바로 그것이다. 남의 코드와, 오픈소스를 많이 봐두면 좋다는 말.. 많이 들었는데 비로서 피부로 느낀다.



 Category, Description 어트리뷰트는 Property에 사용된다. 일반적으로 WinForm 컨트롤을 사용자 환경에 맞게 서브클래싱 할 때 VisualStudio 의 도움을 받을 수 있도록 사용한다.


 다음과 같이 Button 컨트롤 클래스를 상속받은 MyButton 클래스를 정의한다.




 프로퍼티를 구현한다.




 MyButton 컨트롤을 폼에 올려 놓았을 때, 컨트롤의 속성창을 보면 기타 라는 항목에 정의한 프로퍼티 목록이 보안다.

솔직히 속성 창에 보이는 것 만으로도 신기했다. 후후..




 속성 창 맨 아래 보면 설명이 나오는데, 프로퍼티에 정의한 XML 주석의 내용이 나올줄 알았다. 너무 쉽게 생각했나?!

아무튼 아무것도 나오지 않는다.

 



 MyButton 에 정의한 프로퍼티에 다음과 같은 어트리뷰트를 추가한다.




Category 어트리뷰트는 속성창에서 해당 프로퍼티가 위치할 항목을 지정한다. 




Description 어트리뷰트는 해당 속성의 출력할 설명을 지정한다.






 이 밖에도 Browsable, DesignerSerializationVisibility 프로퍼티가 사용되어지는 것 같은데 자료 찾기가 좀 어렵다.. 아직 MSDN이 어색하다..


2010년 9월 11일 토요일

List Constructor : List(Of T) Constructor (IEnumerable(Of T))

C# List<T> Constructor :

List(Of T) Constructor (IEnumerable(Of T))





 Generic Collection 의 등장으로 참 편하게 코딩하고 있다. MFC 할때는... 허흑;;

List<T> 를 아마 가장 많이 사용하지 않을까 싶다. List<T> 생성자는 3가지가 존재한다.




 매개변수가 없는 생성자와, int 인수를 갖는 생성자는 다들 잘 알고 있을테고.. 인수로 IEnumerable<T> 를 갖는 생성자에 대해 테스트해 보았다.

 IEnumerable<T> 인터페이스를 구현한 컬렉션을 인자로 받는다. 인자로 받은 컬렉션의 요소들을 복사하여 새로운 컬렉션 객체를 만든다. 복사라는 말이나왔다. 과연 Deep Copy 일까 Shallow Copy 일까?

테스트 코드는 다음과 같다.




     internal class Product


    {


        public string Name { get; set; }


        public int Cost { get; set; }


    }








    class Program


    {


        static void Main(string[] args)


        {


            var product1 = new List<Product>() { new Product() { Name = "P1a", Cost = 1 }, 


                                                           new Product() { Name = "P2a", Cost = 2 }};





            foreach (Product item in product1)            


                Console.WriteLine("{0}, {1}", item.Name, item.Cost);





            Console.WriteLine(Environment.NewLine);


            var product2 = new List<Product>(product1);


            foreach (Product item in product2)


                Console.WriteLine("{0}, {1}", item.Name, item.Cost);





            Console.WriteLine(Environment.NewLine);


            foreach (Product item in product1)


            {


                item.Name = item.Name + "Edit";


                Console.WriteLine("{0}, {1}", item.Name, item.Cost);


            }





            Console.WriteLine(Environment.NewLine);


            foreach (Product item in product2)


                Console.WriteLine("{0}, {1}", item.Name, item.Cost);   


        }


    }




출력 결과







 예제에서 보면은 product1 List 객체를 이용하여 product2를 생성하였다. 완전히 다른 두 객체일 거라 생각을 했지만 product1 의 내부 요소를 수정하니까 product2 의 내부 요소도 변경이 되었다. 즉, product1 과 product2 라는 두개의 객체가 생성 되었지만 내부적인 요소는 같은 참조 객체를 가리키고 있다는 것이다.

 List<T>(Enumerable(T)) 를 사용할 때, 완전히 다른 사본이 생성된게 아님을 주의하며 코딩해야 겠다.