반응형

이번장에는 계산기의 숫자판에 대한 Layout를 코딩해 보도록 하겠습니다. 

숫자판은 Text 위젯을 사용할 것이고 이 Text 위젯은 Container 라는 위젯을 담기위한 위젯안에 들어가게 됩니다. 

Container 위젯은 내부에 또 다른 위젯을 담기위해 사용하고 위젯은 1개만 넣을 수 있습니다. 

 

1. Container 위젯 코드 

Container
(
    padding: EdgeInsets.all(30),  
    alignment: Alignment(1.0, 1.0),  
    color: Colors.black,
    height: (MediaQuery.of(context).size.height - MediaQuery.of(context).padding.top) * 0.30,
    child: displayText(caption: '$displayNumber', fontsize: displayFontSize,),       
),

  (1) 코드 설명  

    * padding: EdgeInsets.all(30),  : Container 내부 모든면에 30 의 값으로 빈 공간을 확보 합니다. 

Container

    * alignment: Alignment(1.0, 1.0),   : 내부 위젯의 위치를 우측 하단으로 설정 

    * color: Colors.black, : 배경색을 검정으로 

    * height: (MediaQuery.of(context).size.height - MediaQuery.of(context).padding.top) * 0.30, :

       Container의 높이를 전체화면의 30% 로 설정합니다. 높이 설정은 핸드폰 마다 화면사이즈가

       틀리기 때문에 핸드폰의 세로값을 불러와서 세로값의 30% 의 크기로 자동 생성됩니다.

    * child: displayText(caption: '$displayNumber', fontsize: displayFontSize,), : Text 위젯을 생성하고 Container에 넣습니다. 

 

  (2) Text 위젯 Class 코드 

class displayText extends StatelessWidget
{
	displayText({super.key, required this.caption, required this.fontsize});
	final String caption ;
	final double fontsize ;

	@override
	Widget build(BuildContext context)
	{
		return Text
		(
			'$caption', 
			style: TextStyle(color: Colors.white, backgroundColor: Colors.black, fontSize: fontsize,),
			textAlign: TextAlign.right,
		) ;
	}
}

  * Container 내부에 들어가는 Text 위젯에 대한 코드입니다. 

    - 생성자의 파라메터로 Text에 들어갈 Caption 과 Text의 폰트 크기값을 받습니다. 

 

2. DesignPage 클래스 내용 수정 : 지난 포스팅에서 일부 작성된 DesignPage 클래스에 Container 를 추가 합니다. 

class DesignPage extends State<MainPage>  
{
  	@override
	Widget build(BuildContext context)
	{
		return Scaffold
		(
			appBar: AppBar(title: Text('Calculator Program'),),
			body: Column
			(
				crossAxisAlignment: CrossAxisAlignment.stretch,
				children: <Widget>
				[
					Container    // 추가된 코드
					(
						padding: EdgeInsets.all(30),
						alignment: Alignment(1.0, 1.0),   // 내부 위젯의 위치를 우측 하단으로 설정 
						color: Colors.black,
                        height: (MediaQuery.of(context).size.height - MediaQuery.of(context).padding.top) * 0.30,   // 화면의 30%를 차지하도록 설정
    					child: displayText(caption: '$displayNumber', fontsize: displayFontSize,),
					),
				],					
			),
			backgroundColor: Colors.black,   
		);
	}
}
반응형
반응형

앱설계 첫번째로 화면 Layout 을 설계 합니다. 

Flutter 는 모든 구성요소들이 Widget 이라는 모듈로 되어 있고 이 모듈들을 화면에 어떻게 배치할 것인가를 설계하는 것에서 프로그램이 시작 됩니다. 어떤 위젯을 사용할 것인지 어떻게 배치할 것인지에 대한 Layout을 먼저 구성 합니다. 

 

1. Layout 설계 : 계산기 앱을 만들기에 앞서 아래 그림과 같이 기본 Layout을 설계 했습니다. 아래 그림에서 블럭 하나하나가 다 Widget 입니다. 물론 더 세세하게 내려가면서 위젯이 더 있지만 앱 설계의 큰 틀은 아래와 같습니다. 

계산기 앱 Layout
기본 layout

2. 화면 Layout 코딩 설계

  C# 이나 visual basic 처럼 예전에는 화면을 디자인 하기위해 별도의 디자인 에디터에서 설계를 했었는데 최근 개발툴의 추세는 코딩으로 UI 설계를 하는 방향으로 변화하는 듯 합니다. 

 

  (1) 프로그램 진입 ( main 함수 ) : Dart 언어도 C 언어처럼 프로그램의 시작을 main 함수에서 시작 합니다. 

    * CalculatorApp() 이라는 위젯을 생성하는 것으로 프로그램이 시작 됩니다.

    * CalculatorApp 은 MaterialApp 을넘겨 받도록 위젯 Class 로 설계 되어 있다. 

import 'dart:io';
import 'package:flutter/material.dart';

void main() 
{
	runApp(const CalculatorApp());
}
//-----------------------------------------------------------------------------------------

  (2) MaterialApp :

    * Material Design Pattern 으로 설계가 진행 되도록 위젯들이 구성되어 진다. 

    * 앱 설계 디자인을 선택하기 때문에 앱의 가장 근본(root) 로 설정이 이루어 진다. 

    * 기본 구조 

           - title : 간단한 앱의 한 줄 설명입니다. ( 특별한 기능은 없음)

           - theme : 앱의 테마를 지정 

           - home : MaterialApp의 기본 경로로 앱 실행 시 가장 먼저 볼 수 있는 화면(Scaffold 위젯을 사용하여 정의)

    * MaterialApp의 클래스 구조는 아래와 같다.  

    * 여기까지 코드 작성이 되었다면 그림을 그리기 위한 도화지는 준비가 된 것입니다. 

class CalculatorApp extends StatelessWidget 
{
	const CalculatorApp({super.key});
  
	@override
	Widget build(BuildContext context)  // This widget is the root of your application.
	{
		return MaterialApp
		(
			title: 'Calculator App',
			theme: ThemeData(primarySwatch: Colors.blue,),
			home: MainPage(),   // 실제 화면UI가 구현되는 widget(Scaffold)
		);
	}
}

  (3) Scaffold : 본격적으로 그림을 그리기에 앞서 도화지의 영역을 나누어 봅니다. 

    * MaterialApp 의 하위 개념으로 home 에서 호출이 되어 화면에 출력이 된다. 

    * 기본 구조 : 기본구조는 크게 3 영역으로 나뉘며 Body를 제외하고 나머지는 생략이 가능하다. 

      - AppBar : 주로 창 이름을 담당하는 영역 설정 

      - Body : 앱 동작의 주요 영역 설정 

      - BottomNavigationBar : 다른 창 으로 이동할 수 있는 버튼들이 있는 영역 설정 

      - 기타 위젯 설정 ( 예 : FloatingActionButton : 창 위에 떠 있는 효과를 주는 버튼) 

    * Scaffold 의 클래스 구조는 아래와 같다.  

class MainPage extends StatefulWidget 
{
	@override
  	DesignPage createState() => DesignPage();
}
//-----------------------------------------------------------------------------------------
class DesignPage extends State<MainPage>  
{
  	@override
	Widget build(BuildContext context)
	{
		return Scaffold
		(
			appBar: AppBar(title: Text('Calculator Program'),),    // 앱의 제목이 표시되는 영역설정
			body: Column    // 앱의 주요 화면UI 영역 설정 (Column 위젯이 사용됨) 
			(
				crossAxisAlignment: CrossAxisAlignment.stretch,
				children: <Widget>
				[
				],					
			),
			backgroundColor: Colors.black,   // 화면의 전체 배경색을 지정
		);
	}
}

 

여기까지 코딩으로 도화지를 준비하고 도화지에 그림그릴 영역을 나누고 제목을 적어 주는것까지 완료가 되었습니다. 

반응형
반응형

* 앱 개발 공부를 해 봐야지 하고 여러 개발 플랫폼과 개발 언어를 봐오던 차에 Flutter 를 접하고 괜찮겠다 라는 생각이 들어 본격적으로 공부를 시작했습니다. 

 

* 저처럼 처음 공부하는 분들 입장에서 최대한 순차적으로 이해해야 할 부분에 대해 정리하는 개념으로 포스팅을 할 예정입니다. 

* 개발언어에 완전 초보이신 분들은 조금 어려울 수도 있겠습니다.

 

1. Flutter 를 선택한 이유는

  (1) C++ 과 C# 언어에 익숙해서 Dart 언어를 쉽게 이해하고 접할 수 있는 부분

  (2) Multi Flaform 개발이 가능하다.

  (3) 한글로 된 도서가 있더라..( 다른 개발툴들은 거의 없는 한글 책이 있어서 구매 했는데 돈이 아까워서 시작 )

  (4) 웹에서 필요한 정보를 얻기 쉽다. 

 

일단 시작 개발툴은 정했고 Dart 라는 생소한 언어를 책을 통해 훝어 보았습니다. 한 2일 정보 보니 C#과 비슷하네요.. 몇몇 상용구나 Dart 만의 문법 몇가지 보니 바로 시작할 수 있겠다 싶어 앱을 하나 만들면서 시작하고자 가장 간단한 계산기를 하나 만들어 보는 것으로 시작했습니다.

2. 계산기 UI 선택 

  (1) 계산기의 UI를 어떻게 할까 생각하다가 일단 공부이니 내 아이폰에 있는 계산기를 레퍼런스로 정했습니다. 

  (2) 일단 공부하는 것이니 만큼 완벽하지는 않겠지만 최대한 비슷하게 해 보려고 했습니다.  

  (3) 설계된 UI 는 아래와 같습니다. 

계산기 UI
왼쪽이 아이폰계산기 오른쪽이 제가 만든 계산기입니다.

 

반응형
반응형
  1. 표준 에디터(IDE에서 제공하는)이외에, 타사의 프로그래밍 에디터를 사용하는 방법
  2. 메뉴의 Toos | Configure Toos...를 선택
  3. ADD 버튼을 click
  4. 임의의 "Title"을 입력
  5. 프로그램에 사용하는 "Program"을 지정(Browse 참조)
  6. 필요한 경우 "Working dir"를 설정
  7. 실행에 필요한 "Parameters"로 $EDAME 매크로를 선택(Macros button) ($EDAME 매크로는 편집중의 파일명을 인수로 한다.)
  8. Tools | ("Titel"에 입력했던 타이틀)을 선택하면 된다.

 

반응형
반응형

장비제어 프로그램의 기본은 통신입니다. 

연결된 디바이스에 따라 통신 인터페이스도 다양합니다. 그중에서도 통신의 가장 기본은 RS232 시리얼 통신 입니다. 

일반적으로 시리얼 통신은 속도도 느리고 많은 데이타를 처리하는 방식은 아니지만 경우에 따라서는 꽤 큰 데이타도 주고 받을 때가 있습니다. 작은 데이타라면 굳이 비동기 방식을 사용하지 않아도 되겠지만 큰 용량의 데이타를 처리할 때는 비동기 방식을 사용해야 할 필요도 있어서 테스트 삼아 한번 구현해 보았습니다. 

 

비동기 프로그래밍을 인터넷에 찾아 보면 대부분 웹 상에서 데이타를 받을때를 많이 설명합니다. 

제 블로그는 아무래도 장비제어를 위주로 설명을 하니 시리얼 통신을 예로 코드를 작성하였습니다. 

 

1. 외부 연결 디바이스 클래스 : 이 코드는 가상의 디바이스를 예를 들어 코딩한 것입니다. 

public class Devices
{
    private System.IO.Ports.SerialPort commPort ;

    public Devices(System.IO.Ports.SerialPort commPort)
    {
        this.commPort = commPort;
    }
    //-------------------------------------------------------------
    public int readData(ref char[] data)    // 디바이스의 데이타를 불러오기 위한 메서드 입니다. 
    {
        commPort.Write("Send your Data.");  // 데이타 송신 요청(이건 디바이스 마다 틀립니다.) 
        Thread.Sleep(5000);                 // 수신 대기 (모든 데이타가 수신될 때까지 대기)
        var receiveCount = commPort.Read(data, 0, data.Length);  // 수신된 데이타를 버퍼로 이동 

        return receiveCount;
    }
}

위의 ReadData 함수에서 Thread.Sleep(5000) 을 한 이유는 데이타 양이 많아서 모든 데이타가 수신될때 까지 오래 기다려야 하는 상황을 만들었습니다. 

 

2. MainForm

   * Main 화면에 Button 하나와 Label 하나를 올려 놓습니다. 

   * Main Form의 Load 이벤트를 구현합니다. 

   * Button 의 click 이벤트를 구현합니다. 

   * 구현된 Main 클래스 

public partial class frmMain : Form
{
    System.IO.Ports.SerialPort Comm5 = new System.IO.Ports.SerialPort();
    Devices device = new Devices(Comm5);

    public frmMain()
    {
        InitializeComponent();
    }

    private void frmMain_Load(object sender, EventArgs e)
    {
        // 통신 시리얼 포트 정의 
        Comm5.PortName = "COM5";
        Comm5.BaudRate = 9600;
        Comm5.Parity = System.IO.Ports.Parity.None;
        Comm5.StopBits = System.IO.Ports.StopBits.One;

        Comm5.Open();
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        // 버튼을 클릭하면 디바이스에서 데이타를 수신합니다. 
        char[] data = new char[20];
        label1.Text = (await Task.Run(() => device.readData(ref data))).ToString();
    }
}

* private async void button1_Click(object sender, EventArgs e)   : 버튼 클릭 이벤트에 async 를 붙여 줍니다. 

* Task.Run 을 이용하여 await 를 붙여서 데이타 수신함수를 호출 합니다. 

 

* 비동기 방식을 사용하지 않으면 Thread.Sleep(5000)  이 발생되는 동안 UI 가 멈추게 됩니다. 

* 하지만 위와 같이 비동기 방식을 사용하면 UI 가 멈추지 않고 계속 동작이 되고 수신된 데이타가 다 들어 오면 결과가 Label에 출력이 됩니다. 

반응형
반응형

계측장비 프로그램을 하다보면 사용자들이 데이타를 엑셀 파일로 저장해 달라는 요구들이 있습니다. 

Excel 파일로 데이타를 저장하기 위해서는 기본적으로 Excel 프로그램이 설치되어 있어야 합니다. 

보통 프로그램은 제작을 해주고 엑셀은 사용자 측에서 설치하도록 요구를 합니다. 하지만 라이센스 문제로 엑셀을 설치하지 않은 경우가 종종 있습니다. 

 

그래서 Excel 프로그램이 설치되어 있지 않아도 Excel  파일로 데이타를 저장할 수 있는 라이브러리가 있어서 예제 프로그램을 올려 놓습니다. 

아래 예제는 C++ Builder 로 만들어져 있습니다. 

Excel_Format.zip
0.15MB

 

관련된 라이브러리 사이트 정보는 아래와 같습니다.

http://www.codeproject.com/Articles/42504/ExcelFormat-Library

반응형
반응형

1. Device :  GP-IB(PCI)FL 

2. 제조사 : 일본 Contec (https://www.contec.com/

3. 국내 판매 : (주)엔씨텍크놀로지(http://www.da-view.com/

4. 사용용도 : GPIB 통신 카드 

5. 제어 클래스 

typedef enum {NONE, CRLF, CR, LF} Delimiters ;
typedef enum {WITHOUT_EOI, WITH_EOI} EOI ;

class TgpibPort
{
private :
protected :
	int FTimeout ;
	virtual void __fastcall setTimeout(const int Timeout) { FTimeout = Timeout ; }  ;
public :
	virtual bool __fastcall Initialize(void) = 0 ;
	virtual int __fastcall gpibPrint(int Address, AnsiString Command) = 0 ;
	virtual int __fastcall gpibInput(int Address, char* Buffer, int* Count) = 0 ;

	__property int Timeout = {read=FTimeout,  write=setTimeout} ;
};


#include "gpibac.h"   // 헤더파일은 드라이버를 설치하면 찾을 수 있습니다. 

class TContecGpib : public TgpibPort
{
private :
	void __fastcall setTimeout(const int Timeout) override final ;
public :
	TContecGpib() ;
	~TContecGpib() ;

	bool __fastcall Initialize(void) override final ;
	int __fastcall gpibSetup(Delimiters delim, EOI eoi) ;
	int __fastcall gpibPrint(int Address, AnsiString Command)  override final ;
	int __fastcall gpibInput(int Address, char* Buffer, int* Count) override final ;
};


TContecGpib::TContecGpib()
{

}
//---------------------------------------------------------------------------
TContecGpib::~TContecGpib()
{
	GpExit();
}
//---------------------------------------------------------------------------
bool __fastcall TContecGpib::Initialize(void)
{
	bool result = false ;
	int		Ifctime ;
	DWORD	Ret, mode ;

	Ret = GpExit();							// Keep off initialize repeat
	Ret = GpIni();							// GP-IB initialize

	if((Ret & 0x00FF) == 0)                 // Check of GpIni
	{
		GpBoardsts(0x0a, &mode);			// Read Master/Slave mode
		if(mode == 0)                       // mode == 0 (master), mode == 1 (slove)
		{
			Ifctime = 1;					// Default
			Ret = GpIfc(Ifctime);           // Interface Clear

			if((Ret & 0x00FF) == 0)         // Check of GpIfc
			{
				Ret = GpRen();              // Sets REN (Remote Enable) to true.
				if((Ret & 0x00FF) == 0) result = true ;          // Check of GpRen
			}
		}
	}

	return result ;
}
//---------------------------------------------------------------------------
void __fastcall TContecGpib::setTimeout(const int Timeout)
{
	DWORD Ret ;

	Ret = GpTimeout(Timeout);      	// Default(10000ms)
	if((Ret & 0x00FF) != 0)
	{
		FTimeout = Timeout ;
    }
}
//---------------------------------------------------------------------------
int __fastcall TContecGpib::gpibSetup(Delimiters Delim, EOI Eoi)
{
	DWORD Ret ;
	int result = 0 ;

	Ret = GpDelim(Delim, Eoi);      	// default delim = 1, Eoi = 1

	if((Ret & 0x00FF) != 0)   result = -1 ;

	return result ;
}
//---------------------------------------------------------------------------
int __fastcall TContecGpib::gpibPrint(int Address, AnsiString Command)
{
	int result = 0, srlen ;
	char* srbuf ;
	DWORD	MyAddr, Cmd[16], Ret;

	Ret = GpBoardsts(0x08, &MyAddr);            // Read MyAddress

	if((Ret & 0x00FF) != 0) result = -1 ;
	else
	{
		try
		{
			Cmd[0] = 2 ;          // Number of talkers and listeners ( = Number of listeners + 1)
			Cmd[1] = MyAddr ;     // Talker address
			Cmd[2] = Address ;    // Listener address

			srlen = Command.Length() ;
			srbuf = new char(srlen + 10) ;

			strcpy(srbuf, Command.c_str()) ;
			Ret = GpTalk(Cmd, srlen, (UCHAR*)srbuf) ;
		}
		__finally
		{
			delete[] srbuf ;
		}

		if(Ret != 0) result = -2 ;
	}

    return result ;
}
//---------------------------------------------------------------------------
int __fastcall TContecGpib::gpibInput(int Address, char* Buffer, int* Count)
{
	int result = 0 ;
	DWORD MyAddr, srlen, Cmd[16], Ret ;

	Ret = GpBoardsts(0x08, &MyAddr);     // Read MyAddress

	if((Ret & 0x00FF) != 0) result = -1 ;
	else
	{
		Cmd[0] = 2;
		Cmd[1] = Address;
		Cmd[2] = MyAddr;

		memset(Buffer, '\0', strlen(Buffer));

		srlen = *Count ;
		Ret = GpListen(Cmd, &srlen, (UCHAR*)Buffer);

		*Count = srlen ;

		if (Ret >= 3) result = -2 ;         // 0,1,2 : Normal completion
	}

	return result ;
}
//---------------------------------------------------------------------------
반응형
반응형

측정된 데이타를 기반으로 데이타와 데이타 사이의 변화를 그라데이션으로 표현해야 하는 일이 있어서 만들어 본 예제 프로그램 입니다. 

 

1. 테스트 프로그램 구현 : 아래 이미지는 테스트 프로그램을 만들어 놓은 실행결과 입니다. 

gradation 테스트
gradation 테스트 프로그램

  (1) Color 버튼 클릭 : Min(Blue), Max(Red) 값을 기준으로 Value 에 해당하는 값을 색으로 표현합니다. 

  (2) button1  클릭 :  위의 9개 데이타를 기준으로 최대(Red), 최소(Blue)값을 찾고 좌측 이미지 영역에서 표와 동일하게 위치의 값을 할당한 후 중간 영역의 값들을 그라데이션 처리하여 표현하게 됩니다.  

 

  (3) 테스트 프로그램 소스파일 입니다. 

Gradation.zip
0.28MB

  (4) 소스는 C++ Builder 6.0 으로 제작되었습니다. 

반응형

+ Recent posts