저번에 소개해 드렸던 오준석의 안드로이드 생존코딩(코틀린 편) 의 9 가지 예제를 Xamarin 으로 구현해보도록 하겠습니다.

 

화면은

 

1. 키와 몸무계를 입력하고 확인 버튼을 누를 화면

2. 자신의 BMI 수치를 알려주는 화면

 

3. 1 과 2를 네비게이션 할 화면

4. 이미지 사용을 위해 사용된 이미지의 주소 정보를 보여주는 화면

 

이렇게 4개의 화면으로 구성된 간단한 프로그램 입니다.

 

프로그램 실행 시 다음과 같이 표시 됩니다.

 

About 탭을 터치하면 이미지 라이선스 정보를 표시합니다.

키와 몸무계를 입력 한 화면 입니다.

 

이후 확인 버튼을 터치하면 BMI 지수를 표시합니다.

이때 Close 버튼을 터치하면 이전으로 돌아갑니다.

 

먼저 프로젝트를 생성합니다. 템플릿은 Xamarin 으로 선택합니다.

 

프로젝트 이름을 입력하고 Create 를 클릭하면 템플릿 선택 화면이 나옵니다.

Tabbed 를 선택하고, Windows 를 체크하고 OK 를 클릭합니다.

MainPage.xaml 을 열어 다음과 같이 주석 처리를 합니다.

오른쪽에 Solution Explorer 에서 다음과 같이 필요 없는 폴더와 파일을 선택해 삭제합니다.

이제 빌드를 반복하며 오류가 나는 부분을 삭제해 줍니다.

 

3~4 번의 빌드와 삭제를 반복하면 필요없는 코드가 삭제됩니다.

 

먼저 키와 몸무게를 입력할 화면을 구성 합니다.

 

Views 폴더 아래 BmiCalc.xml 라는 이름의 ContentPage 를 만들어 줍니다. 이때 [Content Page (C#)] 이 아니라 [Cont Page] 임을 주의 합니다.

 

되도록 MVVM 패턴으로 개발하기 위해 ViewModels 폴더 아래 BmiCalcViewModel.cs 라는 이름에 Class 를 만들어 줍니다.

BmiCalcViewModel.cs 를 다음과 같이 수정 합니다.

//class BmiCalcViewModel
public class BmiCalcViewModel : BaseViewModel
{
}

 

BmiCalc.xaml 에 네임스페이스를 추가 합니다.

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

위 코드 아래 다음과 같이 네임스페이스를 추가 합니다.

 

xmlns:vm=" 까지 입력하면 인용부호가 하나 더 생기며 다음과 같이 네임스페이스를 선택할 수 있는 목록이 생깁니다.

네임 스페이스가 추가된 코드 입니다.

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:BmiCalculator_Example.ViewModels"

 

<ContentPage.Content> 위에 다음과 같이 삽입 합니다.

<ContentPage.BindingContext>
	<vm:BmiCalcViewModel/>
</ContentPage.BindingContext>

<StackLayout> 안에 <Label> 을 다음으로 교체 합니다.

<Entry x:Name="txtHeight" Margin="10" Keyboard="Numeric" Placeholder="170" />
<Entry x:Name="txtWeight" Margin="10" Keyboard="Numeric" Placeholder="60" />
<Button x:Name="btnStart" Text="확인" Margin="10" />

 

완성된 화면 입니다.

 

MainPage.xaml 에서 좀 전에 주석처리한 부분을 다음과 같이 변경합니다.

<!--
	<views:ItemsPage />
-->
<views:BmiCalc />

 

이제 BMI 지수를 보여주는 화면을 만들기 위해 필요한 이미지를 다운받습니다.

무료로 아이콘을 다운로드 할 수 있는 https://www.flaticon.com 사이트를 이용했습니다. (이미지 사용시 반드시 이미지 출처를 게시해야 합니다. 이후 만들 화면인 AboutPage.xaml 에 추가하도록 합니다. )

 

사용된 3개의 이미지 주소는 다음과 같습니다.

 

https://www.flaticon.com/free-icon/happiness_158409

 

Happiness free vector icons designed by Freepik

Download this free icon in SVG, PSD, PNG, EPS format or as webfonts. Flaticon, the largest database of free vector icons.

www.flaticon.com

 

https://www.flaticon.com/free-icon/sad_158391

 

Sad free vector icons designed by Freepik

Download this free icon in SVG, PSD, PNG, EPS format or as webfonts. Flaticon, the largest database of free vector icons.

www.flaticon.com

https://www.flaticon.com/free-icon/embarrassed_158432

 

Embarrassed free vector icons designed by Freepik

Download this free icon in SVG, PSD, PNG, EPS format or as webfonts. Flaticon, the largest database of free vector icons.

www.flaticon.com

각각의 이미지를 다운로드 한 후 다음과 같이 배치합니다.

Main Project
Android
Universal Windows

 

IOS 에 경우엔 Asset Catalogs 아래 Assets 를 더블 클릭하여 Assets 화면을 열어줍니다.

Assets 화면에 왼쪽에 + 버튼을 클릭한 후 Add Image Set 를 클릭 합니다.

Images 라는 항목이 추가 됩니다. F2 를 눌러 이름을 happiness 로 변경합니다.

다운받은 happiness.png 를 1X 에 끌어다 놓습니다. 다음과같이 1X 에 아이콘이 반영됩니다.

마찬가지로 다른 두개의 아이콘도 반영합니다.

 

ViewModels 폴더 아래 ViewBmiViewModel.cs 라는 클래스를 생성 한 후 클래스 선언부를 수정합니다.

//class ViewBmiViewModel
public class ViewBmiViewModel : BaseViewModel

클래스 선언부 아래 다음과 같은 프로퍼티를 추가 합니다.

public ICommand CloseViewCommand { get; }

public ImageSource BmiImageSource { get; set; } = ImageSource.FromFile(@"");

public string BmiResult { get; set; } = "결과 표시";

다음과 같은 오류가 발생 합니다.

오류가 발생한 부분에 마우스 커서를 가져가 대면 노란 전구 모양이 나타납니다. 전구 모양 오른쪽에 아래쪽 화살표를 클릭하면 해결방법 등이 표시됩니다.

대부분 첫번째를 선택하면 됩니다. 여기서는 using 를 추가합니다. 자바의 import 와 동일하다고 생각하면 됩니다.

마찬가지로 ImageSource 의 오류도 동일하게 해결 합니다.

 

디자이너에서 화면을 볼 수 있도록 임시 이미를 등록 합니다.

@"" 의 인용부호 사이에 솔루션 탐색기의 happiness.png 를 끌어다 놓습니다.

public ImageSource BmiImageSource { get; set; } = ImageSource.FromFile(@"D:\Users\main-note\source\repos\BmiCalculator\BmiCalculator\BmiCalculator\happiness.png");

BmiImageSource 프로퍼티 아래 기본 생성자를 입력합니다.

public ViewBmiViewModel()
{

}

 

 

Views 폴더 아래 ViewBmi.xaml 라는 이름의 ContentPage 를 생성합니다.

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

위 코드 아래 xmlns(xml 네임 스페이스) 를 삽입합니다.

다음과 같이 작성 하면 네임스페이스를 선택할 수 있는 목록이 나옵니다. ViewModels 를 추가합니다.

네임스페이스가 추가 된 모습입니다.

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:vm="clr-namespace:BmiCalculator_Example.ViewModels"

 

<ContentPage.Content> 위에 다음과 같이 삽입 합니다.

<ContentPage.BindingContext>
    <vm:ViewBmiViewModel/>
</ContentPage.BindingContext>

<StackLayout> 을 지우고 다음의 코드로 대체 합니다.

<Grid>
	<StackLayout Margin="0,20,0,0">
		<Image Margin="10" x:Name="image" BackgroundColor="White" Source="{Binding BmiImageSource}" Aspect="AspectFit" HorizontalOptions="Center" VerticalOptions="Center" WidthRequest="50" HeightRequest="50"/>
		<Label Margin="10" x:Name="lblBmi" Text="{Binding BmiResult}" HorizontalTextAlignment="Center"/>
		<Button x:Name="btnClose" Text="Close" Margin="10" Command="{Binding CloseViewCommand}"/>
	</StackLayout>
</Grid>

 

완성된 화면 입니다.

 

??? 캡처하고 보니 Grid.RowDefinitions 을 쓰지 않고 그냥 StackLayout 를 넣었네요. RowDefinitions 를 사용하면 화면을 3 분할을 해서 더 보기 편했을것 같은데.

 

... 다시 캡처하기 귀찮으니 그냥 넘어 갑니다.

다만 저는 xaml 을 다음과 같이 수정 했습니다.

<ContentPage.Content>
<Grid>
	<Grid.RowDefinitions>
		<RowDefinition Height="5*"/>
		<RowDefinition Height="3*"/>
		<RowDefinition Height="2*"/>
	</Grid.RowDefinitions>
	<Image Grid.Row="0" Margin="10" x:Name="image" BackgroundColor="White" Source="{Binding BmiImageSource}" Aspect="AspectFit" HorizontalOptions="Center" VerticalOptions="Center" />
	<Label Grid.Row="1" Margin="10" x:Name="lblBmi" VerticalOptions="Center" Text="{Binding BmiResult}" HorizontalTextAlignment="Center" FontSize="Large"/>
	<Button Grid.Row="2" x:Name="btnClose" Text="Close" Margin="10" />
</Grid>

수정 하는 김에 ViewBmiViewModel.cs 도 수정했습니다.

//public string BmiResult { get; set; } = "결과 표시";
public string BmiResult { get; set; } = "정상";

 

그냥 캡처 했습니다...

다시 수정된 화면입니다. 

훨씬 낫네요.

 

 

 

이제 키와 체중을 입력하고 확인 버튼을 누르면 수치를 보여주는 화면으로 이동하도록 이벤트를 연결해 보겠습니다.

BmiCalcViewModel.cs 파일에 BmiCalcViewModel 클래스 안에 다음 코드를 삽입 합니다.

public string Height { get; set; }
public string Weight { get; set; }

public ICommand CalcBmiCommand { get; }

public BmiCalcViewModel()
{
	Title = "Bmi Calculator";

	double dHeight, dWeight;

	CalcBmiCommand = new Command(async () =>
	{
		if (double.TryParse(Height, out dHeight) != true ||
		double.TryParse(Weight, out dWeight) != true)
		{
			return;
		}

		ViewBmi viewBmi = new ViewBmi();

		((ViewBmiViewModel)viewBmi.BindingContext).Calculator(dHeight, dWeight);

		await Application.Current.MainPage.Navigation.PushModalAsync(viewBmi);
	});
}

Height 와 Weight 는 텍스트박스와 바인딩을 위한 프로퍼티 입니다. 데이터 멤버변수가 아닙니다.

때문에 public string Height = ""; 나 Height; 로 입력하시면 바인딩이 되지 않습니다.

ICommand, Command, ViewBmi 에서 오류가 발생합니다. 위에서 해결했던 방법으로 해결하시면 됩니다.

 

위 3 가지 류를 해결 한 후에도 Calculator(dHeight, dWeight); 에서 오류가 납니다. 아직 구현하지 않았기 때문입니다.

우선은 위 2개의 프로퍼티와 커맨드를 바인딩 합니다.

 

BmiCalc.xml 파일을 열어 다음과 같이 바인딩 합니다.

<StackLayout VerticalOptions="Center">
	<Entry x:Name="txtHeight" Margin="10" Keyboard="Numeric" Placeholder="170" Text="{Binding Height}" />
	<Entry x:Name="txtWeight" Margin="10" Keyboard="Numeric" Placeholder="60" Text="{Binding Weight}" />
	<Button x:Name="btnStart" Text="확인" Margin="10" Command="{Binding CalcBmiCommand}" />
</StackLayout>

하는김에 StackLayout 에 VerticalOption 에 Center 옵션을 주어 중앙으로 옮겼습니다.

 

이제 Calculator 메소드의 구현을 위해 ViewBmiViewModel.cs 파일을 수정합니다.

ViewBmiViewModel() 생성자 아래에 다음에 메소드를 추가 합니다.

public void Calculator(double dHeight, double dWeight)
{
	double dBmi = dWeight / Math.Pow(dHeight / 100.0, 2.0);

	if (dBmi >= 35)
	{
		BmiResult = "고도비만";
	}
	else if (dBmi >= 30)
	{
		BmiResult = "2단계 비만";
	}
	else if (dBmi >= 25)
	{
		BmiResult = "1단계 비만";
	}
	else if (dBmi >= 23)
	{
		BmiResult = "과체중";
	}
	else if (dBmi >= 18.5)
	{
		BmiResult = "정상";
	}
	else
	{
		BmiResult = "저체중";
	}

	if (dBmi >= 23)
	{
		BmiImageSource = ImageSource.FromFile("sad.png");
	}
	else if (dBmi >= 18.5)
	{
		BmiImageSource = ImageSource.FromFile("happiness.png");
	}
	else
	{
		BmiImageSource = ImageSource.FromFile("embarrassed.png");
	}

	OnPropertyChanged("BmiImageSource");
	OnPropertyChanged("BmiResult");
}

BMI 지수를 계산하고 결과 텍스트와 이미지를 변경하는 로직 입니다.

변경 내용을 반영하기 위해 BaseViewModel.cs 에 있는 OnPropertyChanged 메소드를 호출합니다.

프로퍼티의 get;set; 부분을 수정하여 자동으로 반영 할 수도 있지만 여기서는 반영을 하는 메소드가 Calculator 밖에 없고, 해당 방식을 사용하면 코드가 더 길어지기에 사용하지 않았습니다.

 

ViewBmi.xaml 파일에서 Close 버튼을 누르면 이전 화면으로 돌아가게 하기 위해 CloseViewCommand 커맨드에 람다식을 연결합니다.

public ViewBmiViewModel() 생성자를 다음과 같이 수정합니다.

public ViewBmiViewModel()
{
	this.CloseViewCommand = new Command(async () => await Application.Current.MainPage.Navigation.PopModalAsync());
}

 

ViewBmi.xaml 파일에 btnClose 에 커맨드를 바인딩 합니다.

<Button Grid.Row="2" x:Name="btnClose" Text="Close" Margin="10" Command="{Binding CloseViewCommand}"/>

 

최종적으로 이미지 라이선스를 명시해야 하는 About 화면을 수정하도록 하겠습니다. 

 

AboutPageViewModel.cs 파일을 다음과 같이 수정합니다.

//OpenWebCommand = new Command(() => Device.OpenUri(new Uri("https://xamarin.com/platform")));
OpenWebCommand = new Command<string>((uri) => Device.OpenUri(new Uri(uri)));

그리고 AboutPage.xaml 을 다음과 같이 수정합니다.

먼저 <ContentPage.Resources> 블럭과 <Grid> 블럭을 삭제 합니다.

<StackLayout Orientation="Vertical" HorizontalOptions="Center" VerticalOptions="Center">
	<StackLayout Orientation="Horizontal">
		<Label Text="Icons made by "/>
		<Label x:Name="lblFreepik">
			<Label.FormattedText>
				<FormattedString>
					<Span Text="Freepik" TextColor="Blue" TextDecorations="Underline">
						<Span.GestureRecognizers>
							<TapGestureRecognizer Command="{Binding OpenWebCommand}" CommandParameter="https://www.flaticon.com/authors/freepik"/>
						</Span.GestureRecognizers>
					</Span>
				</FormattedString>
			</Label.FormattedText>
		</Label>
	</StackLayout>
	<StackLayout Orientation="Horizontal">
		<Label Text="from "/>
		<Label x:Name="lblFlaticon">
			<Label.FormattedText>
				<FormattedString>
					<Span Text="www.flaticon.com" TextColor="Blue" TextDecorations="Underline">
						<Span.GestureRecognizers>
							<TapGestureRecognizer Command="{Binding OpenWebCommand}" CommandParameter="https://www.flaticon.com/"/>
						</Span.GestureRecognizers>
					</Span>
				</FormattedString>
			</Label.FormattedText>
		</Label>
	</StackLayout>
	<StackLayout Orientation="Horizontal">
		<Label Text="is licensed by "/>
		<Label x:Name="lblCC30">
			<Label.FormattedText>
				<FormattedString>
					<Span Text="CC 3.0 BY" TextColor="Blue" TextDecorations="Underline">
						<Span.GestureRecognizers>
							<TapGestureRecognizer Command="{Binding OpenWebCommand}" CommandParameter="http://creativecommons.org/licenses/by/3.0/"/>
						</Span.GestureRecognizers>
					</Span>
				</FormattedString>
			</Label.FormattedText>
		</Label>
	</StackLayout>
</StackLayout>

단순이 라벨에 커맨드를 바인딩 하여 커맨드 파라미터를 넘겨주면, 해당 주소를 브라우저로 열게 하는 코드 입니다. 

 

복잡해 보이지만 사실 라벨과 속성의 반복일 뿐입니다. 

 

이제 프로그램을 각 에뮬레이터에서 실행해 보겠습니다.

 

Android 결과 화면

Freepik 를 터치 해보겠습니다.

브라우저로 연결하는군요. 정상입니다. 

 

아이폰에서 테스트 하기 위해 아이폰 시뮬레이터를 선택해야 합니다. 여기서는 맥북, 맥 또는 가상 맥 환경과 연결되어 있다고 가정합니다.

IOS 프로젝트를 선택합니다.

그리고 iPhone 을 iPhoneSimulator 로 변경합니다.

그 후 에뮬레이팅 할 단말기 기종을 선택합니다. 시뮬레이터는 리소스를 많이 먹기 때문에 (그리고 속도도 느리고 반응도 느리기 때문에) 5s 로 선택 했습니다.

iPhone 결과 화면

이 화면 에서는 시게가 가려지는 문제가 있네요. 

 

마찬가지로 링크를 클릭하면 해당 사이트로 이동 했습니다. 사파리 하나밖에 없어서 선택 없이 바로 넘어가는군요.

 

참고로 iPhone 시뮬레이터는 입력 반응도 제때 안오고 컨트롤을 떠난 다음에야 입력이 된다던가, About 탭을 클릭해도 바로 안보이고 Browse 를 눌러야 다시 보이는등이 버그가 있습니다. 

이는 입력 후 화면이 바로 반영이 안되서 그런것 같습니다. 나중에 실제 Pad 로 테스트를 해보도록 하겠습니다.  

 

다음은 Universal Windows 윈도우 앱으로 테스트를 해보겠습니다.

 

UWP(Universal Windows) 결과 화면

역시 정상적으로 잘 동작 합니다. 

 

아직 문제는 남아있습니다. iPhone 에서 시계가 가려진다는 점과 Android 에서 가로모드일때 버튼 등이 가려지는 문제가 확인 되었습니다.

 

또한 키 입력 컨트롤에서 Done 버튼 혹은 엔터를 눌렀을때 Weight 입력 컨트롤로의 이동, Weight 컨트롤에서 Done 버튼 혹은 엔터를 눌렀을때 바로 ViewBmi 화면으로 넘어가도록 하는 기능 등이 구현되지 않았습니다.

 

이부분은 추후에 다시 알아보기로 하겠습니다. 

 

 

 

 

http://www.hanbit.co.kr/store/books/look.php?p_code=B6910482773

 

오준석의 안드로이드 생존코딩(코틀린 편)

이 책은 기본을 빠르게 익히고 나서 현업에서 사용하는 라이브러리와 프레임워크로 앱을 효과적으로 만드는 방법을 알려주는 입문 + 활용서입니다. 코틀린을 몰라도, 안드로이드를 몰라도 안드로이드 앱을 만들 수 있습니다. ‘코틀린 입문 + 안드로이드 SDK 입문 + 실전 앱 개발’을 한 권으로 전달하니까요.

www.hanbit.co.kr

코틀린을 공부히가 위해 참고했던 책입니다.

 

9 가지에 예제를 만들어 가며 코틀린에 대한 개념을 익힐 수 있었습니다.

 

다만, 기본적인 내용이 대부분이라, 쓰레드 안정성이랄지, API 함수의 내부 동작 방식이라던지 깊이있는 부분은 따로 찾아서 공부해야 합니다. 

 

예제가 참 맘에 들어서 재미있게 따라했는데, Xamarin 으로 똑같은 예제를 만들어 보면 Xamarin 공부하는데 좋겠다는 생각이 들었습니다. 

 

최근 바쁜일도 없으니 시간이 될때 마다 만들어 보겠습니다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package com.common.test;
 
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
 
import javax.crypto.Cipher;
 
public class RsaSample {
 
    public static String ByteArrayToHexString(byte[] ba) {
        if (ba == null || ba.length == 0) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (final byte b : ba) {
            sb.append(String.format("%02X", b & 0xff));
        }
        
        return sb.toString().substring(0, sb.toString().length()).toUpperCase();
    }
 
    public static void main(String[] args) {
        
        try {
            
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            //keyPairGenerator.initialize(1024);
            //String plain = String.format("%117s", "TEST MESSAGE");
            
            //keyPairGenerator.initialize(2048);
            //String plain = String.format("%245s", "TEST MESSAGE");
            
            keyPairGenerator.initialize(4096);
            String plain = String.format("%501s""TEST MESSAGE");
            
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
            
            RSAPublicKey pub = (RSAPublicKey) keyPair.getPublic(); 
            RSAPrivateKey pvt = (RSAPrivateKey) keyPair.getPrivate();
            
            System.out.println(pub);
            System.out.println(pvt);
            
            BigInteger biModulus = pub.getModulus();
            BigInteger biExponent = pub.getPublicExponent();
            
            BigInteger biPrivateExponent = pvt.getPrivateExponent();
            
            byte [] bModulus = biModulus.toByteArray();
            String strModulus = ByteArrayToHexString(bModulus);
 
            byte [] bExponent = biExponent.toByteArray();
            String strExponent = ByteArrayToHexString(bExponent);
 
            byte [] bPrivateExponent = biPrivateExponent.toByteArray();
            String strPrivateExonent = ByteArrayToHexString(bPrivateExponent);
            
            System.out.printf("Modulus: [%s]\n", strModulus);
            System.out.printf("Exponent: [%s]\n", strExponent);
            System.out.printf("Private Exponent: [%s]\n", strPrivateExonent);
            
            BigInteger modulus = new BigInteger(strModulus, 16);
            BigInteger exponent = new BigInteger(strExponent, 16);
            BigInteger privateExponent = new BigInteger(strPrivateExonent, 16);
    
            byte[] encrypted = null;
            byte[] decrypted = null;
        
            PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, exponent));
            PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new RSAPrivateKeySpec(modulus, privateExponent));
 
            System.out.println(publicKey);
            System.out.println(privateKey);
 
            //RSA/ECB/PKCS1Padding (1024, 2048)
            //RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024, 2048)
            //RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024, 2048)
            Cipher encCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            encCipher.init(Cipher.ENCRYPT_MODE, publicKey);
            
            System.out.printf("Plain Data: [%s]\n", plain);
            
            encrypted = encCipher.doFinal(plain.getBytes());
            
            Cipher decCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            decCipher.init(Cipher.DECRYPT_MODE, privateKey);
            decrypted = decCipher.doFinal(encrypted);
            
            String hexEncrypted = ByteArrayToHexString(encrypted);
            System.out.printf("Encrypted Data: [%s]\n", hexEncrypted);
            
            String strDecrypted = new String(decrypted);
            System.out.printf("Decrypted Data: [%s]\n", strDecrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}
cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.common.test;
 
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
 
public class QueueTest {
    
    static final int MAX_SIZE = 1000;
    static Queue<String> queue = new LinkedBlockingQueue<String>(MAX_SIZE);
    
    static final int THREAD_COUNT = 500;
    
    static class addThread implements Runnable {
        
        String strParameter = null;
        
        addThread(String parameter){
            strParameter = parameter;
        }
        
        public void run() {
            try {
                while(true) {
                    if(queue.size() < MAX_SIZE) {
                        queue.offer(strParameter);
                        System.out.printf("offer %s, size %d\n", strParameter, queue.size());
                    }
                    Thread.sleep(10);
                }
            } catch(Exception ex) {
                ex.printStackTrace();
            }    
        }
    }
    
    static class removeThread implements Runnable {
        
        public void run() {
            try {
                while(true) {
                    if(!queue.isEmpty()) {
                        System.out.printf("poll %s, size %d\n", queue.poll(), queue.size());
                    }
                    Thread.sleep(10);
                }
            } catch(Exception ex) {
                ex.printStackTrace();
            }
        }
    }
        
    public static void main(String[] args) {
        try {
            
            for(int i = 0; i < THREAD_COUNT; i++) {
                Runnable runnable = new addThread(i + "");
                Thread thread = new Thread(runnable);
                thread.start();
            }
            
            for(int i = 0; i < THREAD_COUNT; i++) {
                Runnable runnable = new removeThread();
                Thread thread = new Thread(runnable);
                thread.start();
            }
            
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
 
}
 
cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
package com.common.test;
 
import java.security.MessageDigest;
 
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
 
public class CryptoTest {
 
    private static String strPlain = "Test Message";
    
    /**
     * Convert byte array to hexadecimal
     *
     * @param buffer
     *            Buffer with bytes
     * @return String with hexadecimal data
     */
    public static String ByteArrayToHexString(byte[] ba) {
        return ByteArrayToHexString(ba, "");
    }
 
    public static String ByteArrayToHexString(byte[] ba, String split) {
        if (ba == null || ba.length == 0) {
            return null;
        }
        StringBuilder sb = new StringBuilder();
        for (final byte b : ba) {
            sb.append(String.format("%02X", b & 0xff));
            sb.append(split);
        }
        return sb.toString().substring(0, sb.toString().length()).toUpperCase();
    }
 
    /**
     * Convert hexadecimal string to byte array
     *
     * @param String
     *            with hexadecimal data
     * @return byte array
     */
    public static byte[] HexStringToByteArray(String hex) {
 
        if (hex == null || hex.length() == 0) {
            return null;
        }
 
        byte[] ba = new byte[hex.length() / 2];
 
        for (int i = 0; i < ba.length; i++) {
 
            ba[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
        }
 
        return ba;
    }
    
    /* AES START -------------------------------------------------------------------------------- */
    
    private static byte [] aesEncrypt(byte [] key, byte [] plain, IvParameterSpec iv) {
        byte [] result = null;
        try {
            Cipher encCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec encSecretKey = new SecretKeySpec(key, 0, key.length"AES");
            
            encCipher.init(Cipher.ENCRYPT_MODE,  encSecretKey, iv);
            result = encCipher.doFinal(plain);
        } catch(Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }
    
    private static byte [] aesDecrypt(byte [] key, byte [] plain, IvParameterSpec iv) {
        byte [] result = null;
        try {
            Cipher decCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec encSecretKey = new SecretKeySpec(key, 0, key.length"AES");
            
            decCipher.init(Cipher.DECRYPT_MODE,  encSecretKey, iv);
            result = decCipher.doFinal(plain);
        } catch(Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }
    
    private static void AesTest() {
        try {
            //byte [] key = new byte[16];
            //byte [] key = new byte[24];
            byte [] key = new byte[24];
            byte [] byteAesIv = new byte[16];
            byte [] encrypted = null;
            String hexEncrypted = null;
            byte [] decrypted = null;
            byte [] plain = strPlain.getBytes();
            String strResult = null;
            
            IvParameterSpec aesIv = new IvParameterSpec(byteAesIv);
            
            encrypted = aesEncrypt(key, plain, aesIv);
            hexEncrypted = ByteArrayToHexString(encrypted);
            System.out.println(hexEncrypted);
            
            decrypted = aesDecrypt(key, encrypted, aesIv);
            strResult = new String(decrypted);
            System.out.println(strResult);
            
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
    
    /* AES END -------------------------------------------------------------------------------- */
    
    /* DESede START -------------------------------------------------------------------------------- */
    
    private static byte [] desEdeEncrypt(byte [] key, byte [] plain, IvParameterSpec iv) {
        byte [] result = null;
        try {
            Cipher encCipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
            SecretKeySpec encSecretKey = new SecretKeySpec(key, 0, key.length"DESede");
            
            encCipher.init(Cipher.ENCRYPT_MODE,  encSecretKey, iv);
            result = encCipher.doFinal(plain);
        } catch(Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }
    
    private static byte [] desEdeDecrypt(byte [] key, byte [] plain, IvParameterSpec iv) {
        byte [] result = null;
        try {
            Cipher decCipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
            SecretKeySpec encSecretKey = new SecretKeySpec(key, 0, key.length"DESede");
            
            decCipher.init(Cipher.DECRYPT_MODE,  encSecretKey, iv);
            result = decCipher.doFinal(plain);
        } catch(Exception ex) {
            ex.printStackTrace();
        }
        return result;
    }
    
    private static void DesEdeTest() {
        try {
            byte [] key = new byte[24];
            byte [] byteDesEdeIv = new byte[8];
            byte [] encrypted = null;
            String hexEncrypted = null;
            byte [] decrypted = null;
            byte [] plain = strPlain.getBytes();
            String strResult = null;
            
            IvParameterSpec desEdeIv = new IvParameterSpec(byteDesEdeIv);
            
            encrypted = desEdeEncrypt(key, plain, desEdeIv);
            hexEncrypted = ByteArrayToHexString(encrypted);
            System.out.println(hexEncrypted);
            
            decrypted = desEdeDecrypt(key, encrypted, desEdeIv);
            strResult = new String(decrypted);
            System.out.println(strResult);
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
    
    /* DESede END -------------------------------------------------------------------------------- */
    
    private static void HashTest() {
        try {
            // MD2, MD5
            // SHA-1, SHA-256, SHA-384, SHA-512
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            md.update(strPlain.getBytes());
            
            byte [] byteDigest = md.digest();
            String hexDigest = ByteArrayToHexString(byteDigest);
            System.out.println(hexDigest);
            
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
    
    private static void HmacTest() {
        try {
            byte [] hmacKey = new byte[16];
            SecretKeySpec macSecretKey = new SecretKeySpec(hmacKey, 0, hmacKey.length"HmacSHA256");
            Mac mac = Mac.getInstance("HmacSHA256");
            
            mac.init(macSecretKey);
            mac.update(strPlain.getBytes());
            byte [] byteMac = mac.doFinal();
            
            String hexDigest = ByteArrayToHexString(byteMac);
            System.out.println(hexDigest);
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        try {
            AesTest();
            
            DesEdeTest();
            
            HashTest();
            
            HmacTest();
            
        } catch(Exception ex) {
            ex.printStackTrace();
        }
 
    }
 
}
 
cs

Check


<beans:beans

xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:beans="http://www.springframework.org/schema/beans"

xmlns:context="http://www.springframework.org/schema/context"

xmlns:p="http://www.springframework.org/schema/p"

xmlns:tx="http://www.springframework.org/schema/tx"

xsi:schemaLocation="

http://www.springframework.org/schema/beans 

http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/context 

http://www.springframework.org/schema/context/spring-context.xsd

http://www.springframework.org/schema/util 

http://www.springframework.org/schema/util/spring-util-3.0.xsd

http://www.springframework.org/schema/tx

http://www.springframework.org/schema/tx/spring-tx.xsd"

>




Error Info


Error LNK1169 one or more multiply defined symbols found SAMPLE C:\SAMPLE\Debug\SAMPLE.dll 1

Error LNK2005 _DllMain@12 already defined in dllmain.obj SAMPLE C:\SAMPLE\mfcs140d.lib(dllmodul.obj) 1




Solution


Project -> Properties -> Configuration Properties -> C/C++ -> Preprocessor -> Preprocessor Definitions


Remove "_USRDLL;"



Error Info


Severity Code Description Project File Line Suppression State

Error C1189 #error:  Building MFC application with /MD[d] (CRT dll version) requires MFC shared dll version. Please #define _AFXDLL or do not use /MD[d] CMM_DES c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\atlmfc\include\afx.h 24



Solution


Add 


#define _AFXDLL 


To stdafx.h

Visual Studio 2015 를 설치중 다음과 같은 오류가 나며 설치가 중단됨.

 

다음 링크에서 답을 찾을 수 있었음.

 

https://social.msdn.microsoft.com/Forums/vstudio/en-US/6a22c9be-afe6-4c3e-a5ec-3545547f70a2/team-explorer-2015-fails-for-visual-studio-2015-fatal-error?forum=vssetup

 

go to the control pannel and UNINSTALL the instances u might have for the Visual C++ Redist 2015 (both x64 and x86, typically u'll only have the x64 one.) and install em manually and separately from the ... well.. vc++ 2015 standalone download links. 

DOWNLOAD THEM BOTH HERE: https://www.microsoft.com/en-us/download/details.aspx?id=48145

 

Julio Magana 가 말하는 내용은 visual C++ 재 배포 패키지를 86,64 버전 둘다 지웠다가 다시 깔라는것.

 

재배포 패키지 다운로드 링크:https://www.microsoft.com/en-us/download/details.aspx?id=48145

 

(Visual Studio 가 일부 설치되어 있다면 삭제) -> 재배포 패키지 86, 64 둘다 삭제 후 재부팅 -> 재배포 패키지 설치 -> Visual Sutdio 설치

 

정상 설치됨.

핸드폰을 바꿨는데 옮겨야 하는 이미지 가 600개 정도 되더군요.

 

매번 찾을때마다 힘들고 해서 분야별로 구분을 하려 했습니다.

 

일일히 사진을 보면서 복사 붙여넣기 하기가 힘들어 프로그램을 만들어 봤습니다.

 

1. 실행 화면

실행하면 위와 같은 화면이 나옵니다. 기존에 저장해둔 카테고리가 있다면 카테고리가 표시됩니다.

카테고리에서 앞에 있는 키는 설정한 단축키를 말합니다. 뒤에 있는 글자는 카테고리명이며 이동할 폴더명 이기도 합니다.

 

 

2. 폴더 선택

분류할 사진이 있는 폴더로 이동합니다.

먼저 Select Folder 버튼을 누르면 폴더 찾아보기란 창이 뜹니다.

그곳에서 사진이 있는 폴더럴 선택하고 확인 버튼을 클릭 합니다.

 

 

3. 이미지 로드

선택한 폴더에 있는 사진 및 움직이는 GIF, MP4 가 목록에 표시됩니다. 버튼 오른 쪽에는 이미지의 개수가 표시됩니다.

 

 

4. 카테고리 추가 

Add 버튼을 클릭하여 카테고리 추가 창을 엽니다.

Key 는 단축키를 말합니다. 영문 알파벳만 지원합니다.

Category 는 이동할 폴더명을 말합니다. 여기서는 M 키를 눌렀을때 현재 이미지를 하위 폴더 "구타움짤" 폴더로 이동하라. 라는 설정 입니다.

 

 

5. 카테고리 추가 완료

카테고리가 추가 됐습니다.

만일 여기서 Save 버튼을 클릭 한다면 다음번 실행시도 현재 생성한 카테고리가 표시됩니다.

 

 

6. 카테고리 삭제

카테고리 삭제를 하기 위해 해당 카테고리를 클릭하면 빨간색 박스 표시가 됩니다.

그 후 Delete 버튼을 클릭하면 해당 카테고리가 삭제 됩니다.

(기존에 이동했던 이미지와 생성된 폴더는 그대로 유지됩니다. )

 

 

7. 카테고리 삭제 완료

카테고리가 삭제 되었습니다.

 

 

8. 이미지 분류 시작

START CATEGORIZATION 버튼을 누르면 버튼의 글씨가 END CATEGORIZATION 으로 변경되면서 목록에 첫번째 이미지가 표시됩니다.

여기서는 해당 이미지를 분노 카테고리로 분류하기 위해 키보드에 B 키를 누릅니다.

 

 

9. 이미지 이동

프로그램 하단에 이미지를 이동했다는 메세지가 표시 됩니다.

이미지 목록에서 해당 이미지의 이름이 사라지고 다음 이미지의 이름이 가장 위로 올라옵니다.

또한 리스트 목록의 첫번째 이미지가 보여집니다.

 

 

10. Ctrl + Z 를 누르면 이동중 오류가 발생하지 않았다는 가정 하에 수행을 취소합니다.

즉 이미 이동한 이미지를 원래의 폴더로 되돌립니다.

 

 

11. 표시를 위해 (구분을 위해) 이동한 이미지의 최종 접근일을 현재로 변경합니다.

 

 

 

Visual Studio 2015 로 작업하였으며 .NET 버전은 4.5.2 입니다.

 

Windows 10 에서 테스트 했습니다.

 

JPG, 움직이는 GIF, MP4 를 지원합니다.

 

실행파일: ImageCategorizationHelper.zip

소스코드: https://github.com/acidkay9731/ImageCategorizationHelper.git

 

+ Recent posts