블로그 이미지
* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/ kaki104

카테고리

List All (551)N
Xamarin Forms (4)
Bot Framework (19)N
Azure (9)
Windows 10 (35)
Facebook News & Tips (158)
Windows App(Universa.. (83)
Windows 8&8.1 (113)
Windows Phone 8 (42)
Silverlight (37)
HTML5 & MVC4 (16)
WPF (1)
Portable Class Library (2)
Uncategorised Tips a.. (3)
Kinect for Windows (2)
ETC (12)
kaki104 Scrap (4)
App News (11)
Total521,821
Today18
Yesterday92

하루 빨리 마무리를 짓고 싶었는데..이제야 회원가입 부분까지 완료를 했다. 전체적으로 소스를 약간씩 수정을 해서 화면 변경부분을 수정했고, 회원 가입 처리를 위한 부분도 추가를 했는데..음..소스가 아주 마음에 꼭들지는 않는다.. 프리즘도 적용하고, RX도 적용해서 전체 적으로 싸악 다시 했으면 좋겠는데..일단 이번 게시판 프로젝은 여기까지 올리고, 당분간은 윈폰 강좌를 올리도록 하겠다.

변경된 부분에 대한 설명은 추가로 하지 않겠다. 대부분 주석을 달아 놓았으니 공부하는데 문제는 없을 것이라고 생각된다. 그리고 궁금한 사항은 리플로 남겨주기를 바란다.



ps. 게시판 프로젝트 강좌를 올리는 것은 어떻게 보면 나에게는 실험적인 것이 였는데.. 개발자들의 특성인 것 같다. 반응이 없으니..좋은지 나쁜지..다행이라고 하면 악플은 하나도 없었던 것 같다. ㅎㅎ
그래서 이 이후에 시작하는 윈폰 강좌는 엄청나게 자세하게 만들게 되었다.
Posted by MVP kaki104

이번에는 로그인 로그아웃 기능을 간단하게 구현했다. 로그인 로그아웃이 되니 이제 절반은 한것 같은 느낌이 난다. 어여 끝내고 진짜 사이트를 오픈을 빨리 해야겠다.



처음 사진은 로그인 하기 전~, 다음 사진은 로그인 하고 난 후~ 구현은 간단하게 2개의 스택패널을 만들어서 로그인 전, 로그인 후 2개를 번갈아가며 보여지도록 만들었다.

1. LoginInfo.cs
모든 뷰모델, 모델에서 임포트 시켜서 사용할 수 있도록 만들었다

using System;
using System.ComponentModel;
using System.ComponentModel.Composition;

//로그인 유저 정보
namespace SL5_BOARD.PublicData
{
    /// <summary>
    /// 로그인 정보
    /// </summary>
    [Export(typeof(LoginInfo))]
    public class LoginInfo : INotifyPropertyChanged
    {
        #region PropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

        int mstMemberIdx;
        /// <summary>
        /// 로그인 유저 idx
        /// </summary>
        public int MstMemberIdx
        {
            get
            {
                return mstMemberIdx;
            }
            set
            {
                if (mstMemberIdx != value)
                {
                    mstMemberIdx = value;
                    OnPropertyChanged("MstMemberIdx");
                }
            }
        }

        string eMail;
        /// <summary>
        /// 로그인 유저 Email
        /// </summary>
        public string EMail
        {
            get
            {
                return eMail;
            }
            set
            {
                if (eMail != value)
                {
                    eMail = value;
                    OnPropertyChanged("EMail");
                }
            }
        }

        string memberName;
        /// <summary>
        /// 로그인 유저 이름
        /// </summary>
        public string MemberName
        {
            get
            {
                return memberName;
            }
            set
            {
                if (memberName != value)
                {
                    memberName = value;
                    OnPropertyChanged("MemberName");
                }
            }
        }

        string nickName;
        /// <summary>
        /// 로그인 유저 닉네임
        /// </summary>
        public string NickName
        {
            get
            {
                return nickName;
            }
            set
            {
                if (nickName != value)
                {
                    nickName = value;
                    OnPropertyChanged("NickName");
                }
            }
        }

        DateTime lastLoginDt;
        /// <summary>
        /// 로그인 일시
        /// </summary>
        public DateTime LastLoginDt
        {
            get
            {
                return lastLoginDt;
            }
            set
            {
                if (lastLoginDt != value)
                {
                    lastLoginDt = value;
                    OnPropertyChanged("LastLoginDt");
                }
            }
        }

        string memberGrade;
        /// <summary>
        /// 로그인 유저 등급
        /// </summary>
        public string MemberGrade
        {
            get
            {
                return memberGrade;
            }
            set
            {
                if (memberGrade != value)
                {
                    memberGrade = value;
                    OnPropertyChanged("MemberGrade");
                }
            }
        }

        int listCount;
        /// <summary>
        /// 게시물 카운트
        /// </summary>
        public int ListCount
        {
            get
            {
                return listCount;
            }
            set
            {
                if (listCount != value)
                {
                    listCount = value;
                    OnPropertyChanged("ListCount");
                }
            }
        }

        int attachCount;
        /// <summary>
        /// 덧글 카운트
        /// </summary>
        public int AttachCount
        {
            get
            {
                return attachCount;
            }
            set
            {
                if (attachCount != value)
                {
                    attachCount = value;
                    OnPropertyChanged("AttachCount");
                }
            }
        }

        /// <summary>
        /// 생성자
        /// </summary>
        public LoginInfo()
        {
            InitLoginInfo();
        }

        /// <summary>
        /// 로그인 정보 초기화
        /// </summary>
        public void InitLoginInfo()
        {
            MstMemberIdx = 0;
            EMail = "";
            MemberName = "";
            LastLoginDt = DateTime.Parse("2000-01-01");
            MemberGrade = "";
            ListCount = 0;
            AttachCount = 0;
            IsLogin = false;
        }

        bool isLogin;
        /// <summary>
        /// 로그인 여부
        /// </summary>
        public bool IsLogin
        {
            get
            {
                return isLogin;
            }
            set
            {
                if (isLogin != value)
                {
                    isLogin = value;
                    OnPropertyChanged("IsLogin");
                }
            }
        }
    }
}

2. MainPageViewModel.cs

일단 방금 만든 클래스를 임포트 시켜야 한다.

        /// <summary>
        /// 로그인 정보 임포트
        /// </summary>
        [Import(typeof(LoginInfo))]
        public LoginInfo LoginIF { get; set; }


임포트를 시켰으니..이넘한테 로그인 처리된 데이터를 넣어 주어야 한다. 그럴려면 이전에 만들어 놓았던 Mst_Member 모델을 사용해야하니 그넘도 임포트를 시킨다.

        /// <summary>
        /// 로그인 처리를 하기위한 멤버마스터 모델 임포트
        /// </summary>
        MstMember mstMemberModel;
        [Import(typeof(MstMember))]
        public MstMember MstMemberModel
        {
            get
            {
                return mstMemberModel;
            }
            set
            {
                mstMemberModel = value;
                mstMemberModel.PropertyChanged += new PropertyChangedEventHandler(MstMemberModel_PropertyChanged);
            }
        }


MstMember 모델 임포트를 시키면서 프로퍼티 체인지 이벤트를 구현했다. 그래야 로그인 후에 데이터를 받아서 처리를 할 수 있기 때문이다.

        /// <summary>
        /// 멤버 마스터 모델의 프로퍼티 체인지 이벤트 구현
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void MstMemberModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "MessageData":
                    //모델에서 발생한 메시지를 뷰모델로 끌어 올려서 통합 적으로 관리
                    MessageData = MstMemberModel.MessageData;
                    break;
                case "LoginMember":
                    if (MstMemberModel.LoginMember != null)
                    {
                        //로그인 성공했을 경우
                        // admin@localhost
                        // p@ssw0rd
                        MST_MEMBER member = MstMemberModel.LoginMember;

                        LoginIF.MstMemberIdx = member.MST_MEMBER_IDX;
                        LoginIF.EMail = member.EMAIL;
                        LoginIF.MemberName = member.MEMBER_NAME;
                        LoginIF.NickName = member.NICK_NAME;
                        LoginIF.MemberGrade = member.MEMBER_GRADE;

                        LoginIF.ListCount = member.BoardLists.Count;
                        LoginIF.LastLoginDt = DateTime.Now;
                        member.LAST_LOGIN_DT = DateTime.Now;
                        LoginIF.IsLogin = true;

                        //마지막 로그인 일자 저장
                        mstMemberModel.Saving();
                    }
                    else
                    {
                        //로그아웃 null값을 가지는 경우는 강제로 넣는 것 말고는 없냉
                        MessageData = "로그아웃 되었습니다.";
                        LoginEmail = "";
                        LoginPassword = "";

                        LoginIF.MstMemberIdx = 0;
                        LoginIF.EMail = "";
                        LoginIF.MemberName = "";
                        LoginIF.NickName = "";
                        LoginIF.MemberGrade = "";
                        LoginIF.ListCount = 0;
                        LoginIF.LastLoginDt = DateTime.Parse("2000-01-01");
                        LoginIF.IsLogin = false;
                    }
                    break;
            }
        }

그리고, 로그인 버튼 클릭했을 때의 로그인 커맨드와 로그아웃 커맨드 2개를 새로 만들어야한다.

        ICommand loginCommand;
        public ICommand LoginCommand
        {
            get
            {
                if (loginCommand == null)
                {
                    loginCommand = new ActionCommand(() =>
                    {
                        //로그인 확인
                        MstMemberModel.Login(LoginEmail, LoginPassword);
                    });
                }
                return loginCommand;
            }
        }

        ICommand logoutCommand;
        public ICommand LogoutCommand
        {
            get
            {
                if (logoutCommand == null)
                {
                    logoutCommand = new ActionCommand(() =>
                    {
                        //로그아웃
                        MstMemberModel.LoginMember = null;
                    });
                }
                return logoutCommand;
            }
        }

로그인 작업에서 사용한 Login() 메소드는 약간 수정이 되었다. 기존에는 loadComplete로 이동을 해서 결과를 처리 했으나, 이번에는 그냥 LoginMember에 데이터를 쑤셔 넣는 역할로 변경되었다.
(메인 페이지에 연결되어있다보니, ConditionData, CurrentData등의 기존 사용중인 것들과 같이 쓸 수가 없어서 변경했다.)

        //로그인
        public void Login(string email, string password)
        {
            //로그인시 이메일과 비밀번호를 쿼리에 넣어서 보내야함.(쿼리도 바꿔주고)
            if (BoardDomainService.Context.IsLoading == false)
            {
                BoardDomainService.Context.Load(
                                                BoardDomainService.Context.GetMstMemberByLoginQuery(email, password),
                                                LoadBehavior.RefreshCurrent,
                                                load =>
                                                {
                                                    if (load.HasError == false)
                                                    {
                                                        MessageData = "로그인 성공";
                                                        LoginMember = load.AllEntities.Cast<MST_MEMBER>().FirstOrDefault();
                                                    }
                                                },
                                                true);
            }
            else
            {
                MessageData = "조회 중이니 잠시 후 다시 시도해 주시기 바랍니다.";
            }
        }

로그아웃 커맨드에서는 LoginMember에 Null 값만 넣으면 자동으로 로그 아웃 처리가 되도록 되어있다. 음..개선의 여지는 있지만..나름 간편한 방법을 사용했다.

3. 기타 수정사항 확인

** 로그인 상태에 따른 스택패널 보이기 숨기기 작업

MainPage.xaml 을 참조

<!--로그인창-->
<Border x:Name="Login" BorderBrush="#FF81C1C7" BorderThickness="4" CornerRadius="10" Visibility="{Binding LoginIF.IsLogin, Converter={StaticResource VisibilityReversConverter}}">

.......

<!--로그인 멤버 정보 보이는 박스 영역-->
<Border x:Name="MemberInfo" BorderBrush="#FF81C1C7" BorderThickness="4" CornerRadius="10" Visibility="{Binding LoginIF.IsLogin, Converter={StaticResource VisibilityConverter}}" d:IsHidden="True">

.......

LoginIF.IsLogin 값에 따라서 보이고 앙보이게 컨버터를 이용해서 처리함

4. 전체 소스를 올리도록 하겠다.
주석이 있으니 참고하면서..소스를 보면 도움이 될 것이라고 생각한다. 소스에서 이해가 앙되거나 추가 설명이 필요한 부분은 리플로 남겨주면 다시 설명을 달도록 하겠다.

Posted by MVP kaki104

이번 강좌는 글쓰기 기능을 구현하도록 하겠다. 강좌가 너무 늦어서 잊혀져 가는 강좌였을 것인데..흐흐;; 필자도 요즘 슬럼프끼가 좀 보여서 어떻게 해야하는지 나름 고민 중인데..이번 강좌에 만은 호응을 기대해 본다.(호응 없으면...더욱더 깊은 슬럼프의 나락으로 떨어질지도..쿨럭)
이번에도 이곳 저것 수정을 해 놔서..중요한 부분은 이곳에서 설명을 하고, 나머지는 소스를 참고 할 수 있도록 소스를 올리도록 하겠다.



1. BOARD_LIST.cs

제일 시간이 오래 걸렸던 부분인데.. Code First로 텍스트 필드를 만들어 놓으면 128Byte의 기본 크기를 가지게 되는데..우리가 글을 작성할때 128바이트는 너무나 작은 사이즈라..이걸 대폭~늘려야 한다. 특히 RichTextBox를 사용해서 xaml을 직접 입출력을 할려고 하면 무지막지하게 커야한다. 그래서 클래스를 고쳤다.

[Column(TypeName="ntext")]
[MaxLength]
public string LIST_CONTENT { get; set; }
이렇게 컬럼을 수정해 놓으면 무척 만은 데이터를 저장 할 수 있게 된다.(정확한 사이즈는 MSDN에서..쿨럭)

추가로 테이블 스키마가 수정되면 전에 우리가 만들어 놓았던 SL5_BOARD_DBCONTEXTInitializer에서 데이터베이스를 Drop하고 다시 만들도록 되어 있었는데..이걸 약간 수정해보자..(데이터베이스 드롭되면 기존 데이터 다 날라가니..아무래도 좀..)

public class SL5_BOARD_DBCONTEXTInitializer
        : CreateDatabaseIfNotExists<SL5_BOARD_DBCONTEXT>

이렇게 변경해 놓으면 데이터베이스 파일이 존재 하지 않을 때만 새로 만들어 준다.

에..그럼 기존 데이터베이스를 보존할려면 수동으로 스키마를 수정해야하는데..Server Explorer를 열어서 SL5_BOARD.sdf 파일을 연결 하고 Tables을 열어서 BOARD_LIST를 오른쪽 마우스로 클릭하면 Edit Table Schema를 선택하고, LIST_CONTEXT의 Data Type을 ntext로 변경하고 OK를 눌러주면 된다.



위의 그림을 참고해서 수정한다.

2. 글쓰기 버튼 클릭~

BoardListViewModel.cs 일부 소스

ICommand addCommand;
/// <summary>
/// 추가 커맨드
/// </summary>
public ICommand AddCommand
{
    get
    {
        if (addCommand == null)
        {
            addCommand = new ActionCommand(() =>
            {
                BOARD_LIST obj = new BOARD_LIST();
                obj.BOARD_MAIN_IDX = BoardMain.CurrentData.BOARD_MAIN_IDX;

                //요기 1은 나중에 로그인 기능이 추가되면 로그인 유저 아이디로 변경
                obj.MST_MEMBER_IDX = 1;
                BoardListData.Adding(obj);

                //수정 상태로 바로 변경

                IsEditting = true;
            });
        }
        return addCommand;
    }
}

BoardList.cs 일부소스

/// <summary>
/// 추가 함수
/// </summary>
/// <param name="addData">BOARD_LIST</param>
public void Adding(Object addData)
{
    if (addData != null && addData is BOARD_LIST)
    {
        BOARD_LIST item = addData as BOARD_LIST;

        //요기 등록일시 넣는 부분과 등록자 아이디 넣는 부분 차후에 위치 변경 예정
        item.REG_DT = DateTime.Now;
        item.REG_IDX = 1;

        //컨텍스트에 넣궁

        BoardDomainService.Context.BOARD_LISTs.Add(item);

        //컨디션데이터에도 넣구
        ConditionData.Add(item);

        //현재 데이터에 넣는다 -> 이렇게 넣으면 화면 변경되는 거 알죠?
        CurrentData = item;

        MessageData = "추가 작업을 완료 했습니다.";
    }
    else
    {
        MessageData = "데이터가 없거나 형식이 다릅니다.";
    }
}


3. 저장 버튼 클릭

리치텍스트 박스의 xaml 프로퍼티는 직접 바인딩을 해보니 오류가 난다;; 그래서 시간 관계상 패수~ ㅋ

ICommand saveCommand;
/// <summary>
/// 저장 커맨드
/// </summary>
public ICommand SaveCommand
{
    get
    {
        if (saveCommand == null)
        {
            saveCommand = new ActionCommand(item =>
            {
                RichTextBox rtb = item as RichTextBox;
                if (rtb != null)
                {

                    //현재 데이터에 LIST_CONTEXT에 리치텍스트박스 xaml프로퍼티 값을 바로 넣음
                    BoardListData.CurrentData.LIST_CONTENT = rtb.Xaml;

                    //그리고 저장
                    BoardListData.Saving();
                }

                //편집상태는 false로
                IsEditting = false;
            });
        }
        return saveCommand;
    }
}


BoardList.cs 일부

/// <summary>
/// 저장 함수
/// </summary>
public void Saving()
{

    //나중에 여기도 좀 바꿔야 하는뎅..에..이번에는 그냥 평범하게..
    BoardDomainService.Context.SubmitChanges(submitCallBack, null);
}

요기는 특별히 할일은 없음

4. 조회 시
뷰모델에 있는 BoardListData의 프로퍼티 체인지 이벤트를 이용해서 CurrentData가 변경되면 그 데이터를 리치텍스트박스에 넣도록 코딩함.

BoardListDetailView.xaml.cs 일부

[Import(typeof(BoardListViewModel))]
public BoardListViewModel BoardListVM
{
    get
    {
        return this.DataContext as BoardListViewModel;
    }
    set
    {
        this.DataContext = value;
        value.BoardListData.PropertyChanged += new PropertyChangedEventHandler(BoardListData_PropertyChanged);
    }
}

void BoardListData_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
        case "CurrentData":
            if (BoardListVM.BoardListData.CurrentData != null)
            {
                if (BoardListVM.BoardListData.CurrentData.LIST_CONTENT != null)
                    rtb.Xaml = BoardListVM.BoardListData.CurrentData.LIST_CONTENT;
                else
                    rtb.Blocks.Clear();
            }
            break;
    }
}

5. 프로그램이 좀 심플하고 깔끔하게 나오면 좋은데..^^;;; ..
당분간 한번 쭈욱~ 만들어서..한번에 쾅~하고 올려야 될 것 같다. 중간 중간 과정을 보여주기 위해서 여러번 나누어서 올리고 있는데.. 진도가 너무 앙나간다;; 지금 처럼 단계별로 올리는 것이 좋은 분들은 리플 남겨 주시면 현재 상태를 유지하고..무플인 경우에는 한번에 왕창 올리도록 하겠다. 으흐흐..왕창~~~~~~~~~~~~~

ps.첨부한 소스는 실버라이트 소스만 있습니다. 서버 소스는 포함되어 있지 않습니다. 참고하세요

Posted by MVP kaki104

이번회에는 목록화면과 상세화면의 전환에 대해서 다루어 보도록 하겠다. 오늘의 목표화면을 먼저 보자


목록화면에 '보기'라는 버튼이 추가가 되었고, '보기'를 클릭하면 상세 화면으로 전환되고 내용이 표시된다. 상세화면은 디자인을 좀더 손을 볼 필요가 있지만...일단 차후에 보는 것으로 정하고 올린다.

 1. 목록 <-> 상세 전환 흐름

BoardListView.xaml : 보기 버튼 클릭 -> BoardListView.xaml.cs : Button_Click 이벤트
(커맨드 엑션이 발생하지 않아서 직접 코딩으로 처리, 아마 실력 부족일듯..^^;;;)
-> BoardListViewModel.cs : SelectChangedOperation(object obj) 실행

* 여기서 BoardListData.CurrentData를 좀전에 그리드에서 선택한 아이템으로 변경
-> BoardList.cs : OnPropertyChanged("CurrentData") 이벤트 발생
-> MainPageViewModel.cs : void BoardListData_PropertyChanged(object sender, PropertyChangedEventArgs e) 실행

* 여기에서 CurrentData가 변경되었을 때 데이터가 존재하는지 여부에 따라서 CurrentViewName을 설정하면 또 프로퍼티 체인지 이벤트가 발생 -> MainPage.xaml.cs에 void MainPageVM_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) 실행

* 그래서 해당 화면으로 변경

2. MainPage.xaml.cs

using System;
using System.Linq;
using System.ServiceModel.DomainServices.Client;
using System.Windows;
using System.Windows.Controls;

using SL5_BOARD.Views;
using SL5_BOARD.ViewModels;
using System.ComponentModel.Composition;

using System.Collections.ObjectModel;
using System.Windows.Data;

namespace SL5_BOARD
{
    public partial class MainPage : UserControl
    {
        //메인 페이지 뷰모델 임포트
        [Import(typeof(MainPageViewModel))]
        public MainPageViewModel MainPageVM {get; set;}

        /// <summary>
        /// 가상 프로퍼티 보드메인뷰
        /// </summary>
        public virtual BoardMainView VBoardMainView { get; set; }

        /// <summary>
        /// 가상 프로퍼티 보드리스트뷰
        /// </summary>
        public virtual BoardListView VBoardListView { get; set; }

        /// <summary>
        /// 가상 프로퍼티 회원관리뷰
        /// </summary>
        public virtual MstMemberView VMstMemberView { get; set; }

        /// <summary>
        /// 가상 프로퍼티 보드리스트상세뷰
        /// </summary>
        public virtual BoardListDetailView VBoardListDetailView { get; set; }

        public MainPage()
        {
            InitializeComponent();

            //임포트 초기화 : 이명령은 뷰단에서 한번 실행되면, 그 하위 컴포넌트 들도 차례대로 인스턴스화 된다.
            CompositionInitializer.SatisfyImports(this);

            //메인페이지 뷰모델을 LayoutRoot의 DataContext에 바인딩
            LayoutRoot.DataContext = MainPageVM;

            MainPageVM.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(MainPageVM_PropertyChanged);

            VBoardMainView = new BoardMainView();
            VBoardListView = new BoardListView();
            VMstMemberView = new MstMemberView();
            VBoardListDetailView = new BoardListDetailView();
        }

        /// <summary>
        /// 메인페이지뷰모델 프로퍼티 체인지 이벤트
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void MainPageVM_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "CurrentViewName":
                    switch (MainPageVM.CurrentViewName)
                    {
                        case "BoardMainView":
                            MainPageVM.CurrentView = VBoardMainView;
                            break;
                        case "BoardListView":
                            MainPageVM.CurrentView = VBoardListView;
                            break;
                        case "MstMemberView":
                            MainPageVM.CurrentView = VMstMemberView;
                            break;
                        case "BoardListDetailView":
                            MainPageVM.CurrentView = VBoardListDetailView;
                            break;
                    }
                    break;
                default:
                    break;
            }
       }

    }
}

3. MainPageViewModel.cs 일부

        /// <summary>
        /// 보드리스트 모델 임포트 - 선택된 게시물 변경
        /// </summary>
        BoardList boardListData;
        [Import(typeof(BoardList))]
        public BoardList BoardListData
        {
            get
            {
                return boardListData;
            }
            set
            {
                boardListData = value;
                BoardListData.PropertyChanged += new PropertyChangedEventHandler(BoardListData_PropertyChanged);
            }
        }

        /// <summary>
        /// 보드리스트 데이터 모델 프로퍼티 체인지 이벤트
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void BoardListData_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "CurrentData":
                    if (BoardListData.CurrentData != null)
                    {
                        CurrentViewName = "BoardListDetailView";
                    }
                    else
                    {
                        CurrentViewName = "BoardListView";
                    }
                    break;
            }
        }

4. BoardList.cs 일부

        BOARD_LIST currentData;
        /// <summary>
        /// 현재 선택된 데이터
        /// </summary>
        public BOARD_LIST CurrentData
        {
            get
            {
                return currentData;
            }
            set
            {
                if (currentData != value)
                {
                    currentData = value;
                    OnPropertyChanged("CurrentData");
                }
            }
        }

5. BoardListViewModel.cs 일부

        ICommand selectChanged;
        /// <summary>
        /// 현재 아이템 변경 커맨드
        /// </summary>
        public ICommand SelectChangedCommand
        {
            get
            {
                if (selectChanged == null)
                {
                    selectChanged = new ActionCommand(obj => SelectChangedOperation(obj));
                }
                return selectChanged;
            }
        }
        /// <summary>
        /// 커맨드 엑션이 발생하지 않아 이곳을 직접 호출
        /// </summary>
        /// <param name="obj"></param>
        public void SelectChangedOperation(object obj)
        {
            if (obj != null && obj is BOARD_LIST)
            {
                BoardListData.CurrentData = obj as BOARD_LIST;
            }
        }

        ICommand listCommand;
        /// <summary>
        /// 목록으로 커맨드
        /// </summary>
        public ICommand ListCommand
        {
            get
            {
                if (listCommand == null)
                {
                    listCommand = new ActionCommand(() =>
                    {
                        BoardListData.CurrentData = null;
                    });
                }
                return listCommand;
            }
        }


6. BoardListView.xaml.cs

using System.ComponentModel.Composition;
using System.Windows;
using System.Windows.Controls;
using SL5_BOARD.ViewModels;

namespace SL5_BOARD.Views
{
    public partial class BoardListView : UserControl
    {
        [Import(typeof(BoardListViewModel))]
        public BoardListViewModel BoardListVM { get; set; }

        public BoardListView()
        {
            InitializeComponent();

            CompositionInitializer.SatisfyImports(this);

            this.DataContext = BoardListVM;

        }

        /// <summary>
        /// 보기 버튼 이벤트 - 커맨드 엑션이 발생하지 않아서 직접 코딩
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            if (this.grdBoardList.SelectedItem != null)
            {
                BoardListVM.SelectChangedOperation(this.grdBoardList.SelectedItem);
            }
        }
    }
}

7. BoardListView.xaml 일부

  <sdk:DataGrid AutoGenerateColumns="False" Grid.Row="2" IsReadOnly="True"
   x:Name="grdBoardList"
   ColumnWidth="*" DataContext="{Binding BoardListData.ConditionData}" ItemsSource="{Binding}">
   <sdk:DataGrid.Columns>
                <sdk:DataGridTextColumn x:Name="bOARD_LIST_IDXColumn" Binding="{Binding BOARD_LIST_IDX, Mode=OneWay}" Header="번호" Width="50" />
    <sdk:DataGridTextColumn x:Name="lIST_TITLEColumn" Binding="{Binding LIST_TITLE}" Header="제목" CanUserSort="True" />
    <sdk:DataGridCheckBoxColumn x:Name="lIST_ATTACH_YNColumn" Binding="{Binding LIST_ATTACH_YN}" Header="첨부" Width="50" />
    <sdk:DataGridTextColumn x:Name="rEG_IDXColumn" Binding="{Binding MstMember.MEMBER_NAME}" Header="작성자"  Width="70"/>
    <sdk:DataGridTemplateColumn x:Name="rEG_DTColumn" Header="작성일" Width="70">
     <sdk:DataGridTemplateColumn.CellTemplate>
      <DataTemplate>
       <TextBlock Text="{Binding REG_DT, StringFormat=yyyy.MM.dd}" VerticalAlignment="Center" />
      </DataTemplate>
     </sdk:DataGridTemplateColumn.CellTemplate>
    </sdk:DataGridTemplateColumn>
    <sdk:DataGridTextColumn x:Name="lIST_VIEW_COUNTColumn" Binding="{Binding LIST_VIEW_COUNT, StringFormat=N0}" Header="조회"  Width="50"/>
                <sdk:DataGridTemplateColumn x:Name="btnDetail" Width="60" Header="상세">
     <sdk:DataGridTemplateColumn.CellTemplate>
      <DataTemplate>
       <Button Content="보기" Click="Button_Click">
        <i:Interaction.Triggers>
         <i:EventTrigger EventName="Click">
                                        <i:InvokeCommandAction Command="{Binding Path=DataContext.SelectChangedCommand, ElementName=LayoutRoot_BoardList}"
                                                                CommandParameter="{Binding SelectedItem, ElementName=grdBoardList}"/>
         </i:EventTrigger>
        </i:Interaction.Triggers>
       </Button>
      </DataTemplate>
     </sdk:DataGridTemplateColumn.CellTemplate>
    </sdk:DataGridTemplateColumn>

            </sdk:DataGrid.Columns>
  </sdk:DataGrid>

8. BoardListDetailView.xaml

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:SL5_BOARD_ViewModels="clr-namespace:SL5_BOARD.ViewModels" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" x:Class="SL5_BOARD.Views.BoardListDetailView"
    mc:Ignorable="d"
    d:DesignHeight="500" d:DesignWidth="600">
    <d:DesignProperties.DataContext>
        <SL5_BOARD_ViewModels:BoardListViewModel/>
    </d:DesignProperties.DataContext>
   
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
         <RowDefinition Height="38"/>
         <RowDefinition Height="35"/>
         <RowDefinition Height="30"/>
         <RowDefinition Height="86"/>
         <RowDefinition />
        </Grid.RowDefinitions>

        <!--메인 타이틀-->
        <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="게시물 상세" FontSize="14.667" FontWeight="Bold" Foreground="{StaticResource ForeColor_Blues}" Margin="8,8,0,8" d:LayoutOverrides="Height"/>
        <!--Line-->
        <Path Data="M0,30 L400,30" Fill="#FF5181B9" Height="3" Margin="0" Stretch="Fill" Stroke="#FF5181B9" UseLayoutRounding="False" VerticalAlignment="Bottom" StrokeLineJoin="Bevel" StrokeThickness="3" d:LayoutOverrides="GridBox"/>
        <TextBox x:Name="txtTitle" Grid.Row="1" TextWrapping="Wrap" FontSize="13.333" Text="{Binding BoardListData.CurrentData.LIST_TITLE, Mode=TwoWay}" FontWeight="Bold" HorizontalAlignment="Left" Style="{StaticResource NormalTextBoxStyle}" Margin="8,4,0,3" IsReadOnly="True"/>
        <!--조회 버튼-->
        <Button x:Name="btnEdit" Content="수정" HorizontalAlignment="Right" Margin="0,6,156,7" Width="70" Grid.Row="1" Height="22"/>
        <Button Content="삭제" Height="22" HorizontalAlignment="Right" Margin="0,6,82,7" x:Name="btnDelete" Width="70" Grid.Row="1" />
        <Button Content="목록" Height="22" HorizontalAlignment="Right" Margin="0,6,8,7" x:Name="btnList" Width="70" Grid.Row="1" >
         <i:Interaction.Triggers>
          <i:EventTrigger EventName="Click">
           <i:InvokeCommandAction Command="{Binding ListCommand, Mode=OneWay}"/>
          </i:EventTrigger>
         </i:Interaction.Triggers>
        </Button>
        <!--Line-->
        <Path Data="M0,30 L400,30" Fill="{StaticResource ForeColor_Kakis}" Stretch="Fill" Stroke="{StaticResource ForeColor_Kakis}" UseLayoutRounding="False" StrokeLineJoin="Bevel" StrokeThickness="3" Grid.Row="1" Height="3" VerticalAlignment="Bottom" Margin="0,0,0,-1" d:LayoutOverrides="GridBox"/>
        <StackPanel Margin="8,5,8,4" Orientation="Horizontal" Grid.Row="2">
         <TextBlock x:Name="txtRegUser" TextWrapping="Wrap" FontSize="13.333" Text="{Binding BoardListData.CurrentData.MstMember.NICK_NAME}" FontWeight="Bold"/>
         <TextBlock x:Name="txtGrade" Margin="0" TextWrapping="Wrap" FontSize="10.667" Text="{Binding BoardListData.CurrentData.MstMember.MEMBER_GRADE}" VerticalAlignment="Center"/>

        </StackPanel>
 
  <!--커맨드패널-->
  <!----생략------> 
 
        <Border Grid.Row="4" BorderThickness="4" BorderBrush="{StaticResource ForeColor_Kakis}" CornerRadius="10" >
            <RichTextBox x:Name="rtb" VerticalScrollBarVisibility="Auto"
         BorderBrush="{x:Null}" Margin="8,10,10,8" TextWrapping="Wrap"
                         SelectionChanged="rtb_SelectionChanged" IsReadOnly="True">
                <Paragraph>
                    <Run Text="{Binding BoardListData.CurrentData.LIST_CONTENT, Mode=TwoWay}"/>
                </Paragraph>
            </RichTextBox>

        </Border>

    </Grid>
</UserControl>

9. 보기 버튼을 눌렀을 때..
 SelectChangedCommand 커맨드가 실행이 앙대는 이유를 찾다가..너무 오래 걸릴 것 같아서 2가지 소스를 같이 올린다. 소스보고 따라하기를 하다가 커맨드가 동작이 된다면 꼭 알려주기를..흐흐흐 언제나 궁금한 내용이나 의견이 있으면 리플로 남겨주기 바란다.
이제는 비가 그만 왔으면 좋겠다....

참고자료

Silverlight RichTextBox Lab
http://msdn.microsoft.com/en-us/gg315161

Posted by MVP kaki104

이번회에는 메인 메뉴, 상단 메뉴들과 각 화면을 연결 시키는 방법에 대해서 알아보도록 하자. 우선 전체적인 흐름에 대해 먼저 정리를 하고, 각 순서에 따라서 소스 받아서 따라가보면 상세한 내용을 파악할 수 있다. (여기 저기 수정을 해서 어디를 얼만큼 수정했는지 기억이 않나는;;;)

1. 전체적인 흐름

1) Loading & 메인 메뉴 클릭 & 게시판 관리 & 회원 관리
MainPage.xaml : 해당 Command 실행  -> MainPageViewModel.cs : 해당 Command 실행
-> CurrentViewName 프로퍼티 설정 : OnPropertyChaned 발생
-> MainPage.xaml.cs : MainPageVM_PropertyChanged 에서 CurrentViewName에 대한 처리
-> MainPageVM.CurrentViewName에 있는 화면을 CurrentView 프로퍼티에 넣는다.
-> MainPage.xaml에 있는 ContentControl의 Content는 MainPagVM.CurrentView에 바인딩되어 있어서, 화면이 변경된다.

2) 왜 이런 복잡한 단계를 거치게 만들었는가?
일단..뷰와 뷰모델은 서로 직접적인 연결관계가 없는 Loosely Coupled 관계를 유지 해야하고, 뷰모델에서 다른 뷰들의 인스턴스 객체를 가지고 있지 않기 위해서 약간 복잡한 구조를 가지도록 만들었다. 그러나, 처음에만 좀 복잡하게 느낄 뿐이지 실제 프로젝트에서는 이런 식으로 자주 사용 된다.
MVVM + WCF RIA Service 일때 데이터가 비동기로 처리되기 때문에 데이터가 오기전에 뷰에서 먼저 이벤트가 발생해서, 특정 처리가 완료가 않되었을 경우, 모델에서 이벤트를 발생시켜서 뷰에서 다시 처리를 하도록 시키는 경우가 필요하기 때문이다.

 3) 뷰들은 왜 Import 시키지 않았나?
모든 뷰들도 [Export(typeof(UserControl))]을 사용해서 Export시킬 수가 있다. 그런데 그렇게하면 뷰안에서 사용했던, CompositionInitializer.SatisfyImports(this); 명령을 사용할 수 없고, 그렇게 되면, 각 뷰 내부에서 임포트를 시킨 ViewModel을 약간 다른 방법으로 임포트를 시켜야 하고 또 다시 뷰모델 내부에있는 모델을 임포트 하는 방법도 약간 바꿔야 한다.( Prism/MEF Container 방식을 참고 하면 된다.) 그러다보니, 소스를 너무 만이 수정을 해야해서, 여기서는 각각의 뷰들을 가상 프로퍼티로 만들어서 사용했다.

 4) 메인 컨텐츠에서 사용했던 ItemsControl을 왜 ContentControl로 변경했나?
temsControl을 사용하면 여러개의 컨텐츠를 계속 붙여가면서 보여줄 수 있다. 그러나, 지금 이 프로젝트처럼 여러개의 뷰를 붙이는 작업을 하게되면, 각 뷰의 높이를 파악할 수 없어서, 뷰 내부 컨텐츠를 위한 세로 스크롤바를 만들 수가 없다.(그냥 화면 자체가 길어져 버린다..스크롤 바도 없이) 그래서 한번에 하나의 뷰만을 보여주기 위해서 ContentControl로 변경 했다. 상단에 있는 회원 관리 화면을 보면 쉽게 이해가 갈 것이다.

 2. 여러가지 방법을 생각하고,
만들어 본다고 강좌가 좀 늦어졌다...그러나.. 아무도 강좌가 언제 올라오는지 궁금해 하지 않아서..더 늦어진 것인지도 모르겠다..^^;;; 요즘은 리플도 없어지고..점점 관심을 잃어가는 것같은데..낚시글이라도 올려야 하나..라는 생각을 가끔하게 된다. 작은 리플 하나가 큰 힘이 된다~
(소스는 용량관계로 실버라이트 소스만 올리도록 하겠다. 다음회에 올릴 상세 페이지 소스도 함께 들어 있다.)

Posted by MVP kaki104

이번회 부터 게시물 목록을 작업 하도록 하겠다. 하지만, 이것도 조회, 추가 등 몇개로 나누어서 올릴 예정이다. 한번에 너무 만은 내용을 올리면 내용의 부담이 되어서 그런지..별로 좋아하지 않는 것같다.(아마도..ㅎㅎ)

이번회에는 모델, 뷰모델, 뷰를 만들고..(여기까지만 해도 소스 양은 상당하다..) 조회를 했을 때 그리드에 데이터 바인딩 하는 부분과 조회 조건 처리하는 방법, 연결된 데이터 조회(작성자명)하는 것들을 알아보도록 하고 시작 하기 전에 목표 화면을 보자~

심플한 화면~ 하하.. 역시나 기본적인 레이아웃은 실버라이트 코리아의 디자인을 카피 했다는..

1. Model

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.ServiceModel.DomainServices.Client;
using SL5_BOARD.Web.Model;

namespace SL5_BOARD.Models
{
    [Export(typeof(BoardList))]
    public class BoardList : INotifyPropertyChanged
    {
        #region PropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

        /// <summary>
        /// Board 도메인 서비스 임포트
        /// </summary>
        [Import(typeof(BoardContext))]
        public BoardContext BoardDomainService { get; set; }

        [Import(typeof(MstMember))]
        public MstMember _mstMember { get; set; }

        ObservableCollection<BOARD_LIST> conditionData;
        /// <summary>
        /// 조건에 맞는 데이터
        /// </summary>
        public ObservableCollection<BOARD_LIST> ConditionData
        {
            get
            {
                return conditionData;
            }
            set
            {
                if (conditionData != value)
                {
                    conditionData = value;
                    OnPropertyChanged("ConditionData");
                }
            }
        }

        BOARD_LIST currentData;
        /// <summary>
        /// 현재 선택된 데이터
        /// </summary>
        public BOARD_LIST CurrentData
        {
            get
            {
                return currentData;
            }
            set
            {
                if (currentData != value)
                {
                    currentData = value;
                    OnPropertyChanged("CurrentData");
                }
            }
        }

        string messageData;
        /// <summary>
        /// 모델 메시지 데이터
        /// </summary>
        public string MessageData
        {
            get
            {
                return messageData;
            }
            private set
            {
                if (messageData != value)
                {
                    messageData = "[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] " + value;
                    OnPropertyChanged("MessageData");
                }
            }
        }

        /// <summary>
        /// 생성자
        /// </summary>
        public BoardList()
        {
            ConditionData = new ObservableCollection<BOARD_LIST>();

            this.PropertyChanged += new PropertyChangedEventHandler(BoardList_PropertyChanged);
        }

        /// <summary>
        /// 프로퍼티 채인지 구현
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void BoardList_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            //모델 프로퍼티 체인지 이벤트 발생시 다른 프로퍼티에 영향을 줘야하는 경우사용
        }

        /// <summary>
        /// 조건 조회
        /// </summary>
        /// <param name="condition">조건</param>
        public void Getting(string condition)
        {
            if (condition.Length == 0)
            {
                //조건이 없을때는 모두 가지고 오는 함수 호출
                BoardDomainService.Context.Load(BoardDomainService.Context.GetBoardListQuery(),
                                                LoadBehavior.RefreshCurrent,
                                                load =>
                                                {
                                                    loadComplete(load);
                                                }, true);

            }
            else
            {
                //조건이 있으면 조건 조회 함수 호출
                BoardDomainService.Context.Load(BoardDomainService.Context.GetBoardListByConditionQuery(condition),
                                                LoadBehavior.RefreshCurrent,
                                                load =>
                                                {
                                                    loadComplete(load);
                                                }, true);
            }
        }

        /// <summary>
        /// Load 콜백 함수
        /// </summary>
        /// <param name="lo"></param>
        private void loadComplete(LoadOperation lo)
        {
            if (lo.HasError == false)
            {
                //조회 결과를 컨디션데이터로 만들고
                ConditionData = new ObservableCollection<BOARD_LIST>(lo.AllEntities.Cast<BOARD_LIST>());

                //컨디션데이터 중에 회원과 연결이 앙되어 있는 카운트를 구하고
                var count = (from list in ConditionData
                             where list.MstMember == null
                             select list).Count();

                //카운트가 0보다 크면 회원데이터를 조회한다.
                if (count > 0)
                {
                    _mstMember.Getting();
                }

                MessageData = "조회 작업을 완료 했습니다.";
            }
            else
            {
                MessageData = "조회 작업이 실패 했습니다.";
            }
        }

        /// <summary>
        /// 추가 함수
        /// </summary>
        /// <param name="addData">BOARD_LIST</param>
        public void Adding(Object addData)
        {
            if (addData != null && addData is BOARD_LIST)
            {
                BOARD_LIST item = addData as BOARD_LIST;
                item.REG_DT = DateTime.Now;
                item.REG_IDX = 1;
                BoardDomainService.Context.BOARD_LISTs.Add(item);
                CurrentData = item;

                MessageData = "추가 작업을 완료 했습니다.(저장을 해야 서버에 반영됩니다)";
            }
            else
            {
                MessageData = "데이터가 없거나 형식이 다릅니다.";
            }
        }
       
        /// <summary>
        /// 삭제 함수
        /// </summary>
        /// <param name="removeData">BOARD_LIST</param>
        public void Removing(Object removeData)
        {
            if (removeData != null && removeData is BOARD_LIST)
            {
                BoardDomainService.Context.BOARD_LISTs.Remove(removeData as BOARD_LIST);

                MessageData = "삭제 작업을 완료 했습니다.(저장을 해야 서버에 반영됩니다)";
            }
            else
            {
                MessageData = "데이터가 없거나 형식이 다릅니다.";
            }
        }

        /// <summary>
        /// 저장 함수
        /// </summary>
        public void Saving()
        {
            BoardDomainService.Context.SubmitChanges(submitCallBack, null);
        }

        /// <summary>
        /// 저장 콜백
        /// </summary>
        /// <param name="so"></param>
        private void submitCallBack(SubmitOperation so)
        {
            if (so.IsComplete == true)
            {
                MessageData = "저장 작업을 완료 했습니다.";
            }
            else
            {
                MessageData = "저장 작업을 실패 했습니다.";
            }
        }

        /// <summary>
        /// 취소 함수
        /// </summary>
        public void Canceling()
        {
            BoardDomainService.Context.RejectChanges();

            MessageData = "취소 작업을 완료 했습니다.";
        }

    }
}

2. ViewModel

using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Windows.Input;
using Microsoft.Expression.Interactivity.Core;
using SL5_BOARD.Models;
using SL5_BOARD.Web.Model;
using System.Windows.Controls;

namespace SL5_BOARD.ViewModels
{
    [Export(typeof(BoardListViewModel))]
    public class BoardListViewModel : INotifyPropertyChanged
    {
        #region PropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

        /// <summary>
        /// 보드리스트 임포트
        /// </summary>
        [Import(typeof(BoardList))]
        public BoardList BoardListData { get; set; }

        /// <summary>
        /// 생성자
        /// </summary>
        public BoardListViewModel()
        {
            this.PropertyChanged += new PropertyChangedEventHandler(BoardListViewModel_PropertyChanged);
        }

        /// <summary>
        /// 프로퍼티 체인지 이벤트 구현
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void BoardListViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            //로컬 프로퍼티 변경시 다른 프로퍼티에 영향을 미치는 경우 코딩
        }

        ICommand selectConditionCommand;
        /// <summary>
        /// 조건 조회 커맨드
        /// </summary>
        public ICommand SelectConditionCommand
        {
            get
            {
                if (selectConditionCommand == null)
                {
                    selectConditionCommand = new ActionCommand(condition =>
                    {
                        //조건을 가지는 조회 함수 실행
                        BoardListData.Getting(condition as string);
                    });
                }
                return selectConditionCommand;
            }
        }

        ICommand addCommand;
        /// <summary>
        /// 추가 커맨드
        /// </summary>
        public ICommand AddCommand
        {
            get
            {
                if (addCommand == null)
                {
                    addCommand = new ActionCommand(() =>
                    {
                        BOARD_LIST obj = new BOARD_LIST();
                        BoardListData.Adding(obj);
                    });
                }
                return addCommand;
            }
        }

        ICommand removeCommand;
        /// <summary>
        /// 삭제 커맨드
        /// </summary>
        public ICommand RemoveCommand
        {
            get
            {
                if (removeCommand == null)
                {
                    removeCommand = new ActionCommand(obj =>
                    {
                        if (obj != null && obj is BOARD_LIST)
                        {
                            BoardListData.Removing(obj);
                        }
                    });
                }
                return removeCommand;
            }
        }

        ICommand saveCommand;
        /// <summary>
        /// 저장 커맨드
        /// </summary>
        public ICommand SaveCommand
        {
            get
            {
                if (saveCommand == null)
                {
                    saveCommand = new ActionCommand(() =>
                    {
                        BoardListData.Saving();
                    });
                }
                return saveCommand;
            }
        }

        ICommand cancelCommand;
        /// <summary>
        /// 취소 커맨드
        /// </summary>
        public ICommand CancelCommand
        {
            get
            {
                if (cancelCommand == null)
                {
                    cancelCommand = new ActionCommand(() =>
                    {
                        BoardListData.Canceling();
                    });
                }
                return cancelCommand;
            }
        }

        ICommand selectChanged;
        /// <summary>
        /// 현재 아이템 변경 커맨드
        /// </summary>
        public ICommand SelectChangedCommand
        {
            get
            {
                if (selectChanged == null)
                {
                    selectChanged = new ActionCommand(obj => SelectChangedOperation(obj));
                }
                return selectChanged;
            }
        }

        private void SelectChangedOperation(object obj)
        {
            if (obj != null && obj is BOARD_LIST)
                BoardListData.CurrentData = obj as BOARD_LIST;
        }
    }
}

3. View

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:SL5_BOARD_ViewModels="clr-namespace:SL5_BOARD.ViewModels"
 xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
 xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
 x:Class="SL5_BOARD.Views.BoardListView"
    mc:Ignorable="d"
    d:DesignHeight="500" d:DesignWidth="600">
   
 <Grid x:Name="LayoutRoot" Background="White">
  <Grid.RowDefinitions>
   <RowDefinition Height="38"/>
   <RowDefinition Height="35"/>
   <RowDefinition/>
   <RowDefinition Height="35"/>
  </Grid.RowDefinitions>

  <Grid.DataContext>
   <SL5_BOARD_ViewModels:BoardListViewModel/>
  </Grid.DataContext>

  <i:Interaction.Triggers>
   <i:EventTrigger>
    <i:InvokeCommandAction Command="{Binding SelectConditionCommand, Mode=OneWay}" CommandParameter="{Binding Text, ElementName=txtCondition}"/>
   </i:EventTrigger>
  </i:Interaction.Triggers>

  <!--메인 타이틀-->
  <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="게시물 목록" FontSize="14.667" FontWeight="Bold" Foreground="{StaticResource ForeColor_Blues}" Margin="10,6,0,10" d:LayoutOverrides="Height"/>
  <!--Line-->
  <Path Data="M0,30 L400,30" Fill="#FF5181B9" Height="3" Margin="0" Stretch="Fill" Stroke="#FF5181B9" UseLayoutRounding="False" VerticalAlignment="Bottom" StrokeLineJoin="Bevel" StrokeThickness="3" Grid.ColumnSpan="2"/>
  <TextBlock Margin="8,6,225,8" Grid.Row="1" TextWrapping="Wrap" Text="{Binding BoardListData.MessageData, Mode=OneWay}" VerticalAlignment="Center" FontSize="12" Height="17.4545459747314"/>
  <TextBox x:Name="txtCondition" TextWrapping="Wrap" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" Style="{StaticResource NormalTextBoxStyle}" HorizontalAlignment="Right" Margin="0,5,82,5" Grid.Row="1" Width="122"/>

  <!--조회 버튼-->
  <Button x:Name="btnSelect" Content="조회" HorizontalAlignment="Right" Margin="0,6,8,7" Width="70" Grid.Column="1" Grid.Row="1" Height="22">
   <i:Interaction.Triggers>
    <i:EventTrigger EventName="Click">
     <i:InvokeCommandAction Command="{Binding SelectConditionCommand, Mode=OneWay}" CommandParameter="{Binding Text, ElementName=txtCondition}"/>
    </i:EventTrigger>
   </i:Interaction.Triggers>
  </Button>
  <!--Line-->
  <Path Data="M0,30 L400,30" Fill="{StaticResource ForeColor_Kakis}" Stretch="Fill" Stroke="{StaticResource ForeColor_Kakis}" UseLayoutRounding="False" StrokeLineJoin="Bevel" StrokeThickness="3" Grid.ColumnSpan="2" Grid.Row="1" Height="3" VerticalAlignment="Bottom" Margin="0,0,0,-1"/>   

 <sdk:DataGrid AutoGenerateColumns="False" Grid.Row="2" IsReadOnly="True"
   x:Name="grdBoardList" RowDetailsVisibilityMode="VisibleWhenSelected"
   ColumnWidth="*" DataContext="{Binding BoardListData.ConditionData}" ItemsSource="{Binding}">
   <sdk:DataGrid.Columns>

                <sdk:DataGridTextColumn x:Name="bOARD_LIST_IDXColumn" Binding="{Binding Path=BOARD_LIST_IDX, Mode=OneWay}" Header="번호" Width="50" />
    <sdk:DataGridTextColumn x:Name="lIST_TITLEColumn" Binding="{Binding LIST_TITLE}" Header="제목" CanUserSort="True" />

    <sdk:DataGridCheckBoxColumn x:Name="lIST_ATTACH_YNColumn" Binding="{Binding LIST_ATTACH_YN}" Header="첨부" Width="50" />

    <sdk:DataGridTextColumn x:Name="rEG_IDXColumn" Binding="{Binding MstMember.MEMBER_NAME}" Header="작성자"  Width="70"/>

    <sdk:DataGridTemplateColumn x:Name="rEG_DTColumn" Header="작성일" Width="70">
     <sdk:DataGridTemplateColumn.CellTemplate>
      <DataTemplate>
       <TextBlock Text="{Binding REG_DT, StringFormat=yyyy.MM.dd}" VerticalAlignment="Center" />
      </DataTemplate>
     </sdk:DataGridTemplateColumn.CellTemplate>
    </sdk:DataGridTemplateColumn>

    <sdk:DataGridTextColumn x:Name="lIST_VIEW_COUNTColumn" Binding="{Binding LIST_VIEW_COUNT, StringFormat=N0}" Header="조회"  Width="50"/>

    </sdk:DataGrid.Columns>
  </sdk:DataGrid>

 </Grid>
</UserControl>

using System.ComponentModel.Composition;
using System.Windows;
using System.Windows.Controls;
using SL5_BOARD.ViewModels;

namespace SL5_BOARD.Views
{
    public partial class BoardListView : UserControl
    {
        [Import(typeof(BoardListViewModel))]
        public BoardListViewModel BoardListVM { get; set; }

        public BoardListView()
        {
            InitializeComponent();

            CompositionInitializer.SatisfyImports(this);

            LayoutRoot.DataContext = BoardListVM;
        }
    }
}

4. 연결된 테이블

BOARD_LIST와 MST_MEMBER 테이블은 서로 연결되어 있다. 이 부분에 대한 Code First 코딩이 앙되어 있으면 작성자명이 출력이 안될 수 있으니 참고 하기 바란다.

5. 언제나 하는 이야기 이지만..
함께 만들어야 의미가 있는 것이다. 일단 목표화면을 보고 자신이 만들어 보고 만들어 보다가 잘 앙되는 부분만 소스를 참고하면 좀더 빠른 실력 향상이 될 수 있으니 참고하도록 한다. 이 화면 이후 부터는 대부분 비슷한 모델 포멧과 뷰모델 포멧을 사용하게 될테니 두 클래스의 형태를 익혀 놓는 것도 좋다. 그리고, 실행하다가 오류가 난다면 꼭 리플로 알려주고, 요청 자료도 리플로 적어주기 바란다.

Posted by MVP kaki104

화면을 만들기는 일찍 만들었는데.. 스크롤바 문제 때문에 해결할려고 발버둥을 치다가 좀 늦어지게 되었다. 아함..그렇다고 해결한 것도 아니고..ㅜㅜ.. 그 문제의 해결을 여러분들이 해주실 것이라 상상하며..시작을 할려고 한다.( 그렇게 생각해도 괜찮겠죠? ^^;;; )

 1. 회원관리 화면

간단한 구성으로 만들었다..다만 오른쪽 회원 상세 정보가 들어가는 곳이 화면 크기에 따라서 스크롤바로 움직이도록 만들려고 하다가 6시간정도 허비하다가 GG치고 올리는 중이지만..

1. SL5_BOARD.Web 프로젝트

SL5_BOARD_DBCONTEXT.cs

using System.Data.Entity;
using System.Web;
using System;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace SL5_BOARD.Web.Model
{
    public class SL5_BOARD_DBCONTEXT : DbContext
    {
        public SL5_BOARD_DBCONTEXT()
            : base("SL5_BOARD")
        {
            //디자인타임이 아닌경우
            if (HttpContext.Current == null)
            {
                //데이터베이스 초기화
                Database.SetInitializer<SL5_BOARD_DBCONTEXT>(new SL5_BOARD_DBCONTEXTInitializer());
            }
        }

        public DbSet<MST_MEMBER> MST_MEMBERS { get; set; }
        public DbSet<BOARD_MAIN> BOARD_MAINS { get; set; }
        public DbSet<BOARD_LIST> BOARD_LISTS { get; set; }
        public DbSet<BOARD_REPLY> BOARD_REPLYS { get; set; }
        public DbSet<BOARD_ATTACH> BOARD_ATTACHS { get; set; }

         //모델이 만들어 지면서..테이블과의 관계를 줄 수 있는데..좀 뒤로 미룬다
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        }
   }

    //데이터베이스를 새로 만들었을 때
    public class SL5_BOARD_DBCONTEXTInitializer : DropCreateDatabaseIfModelChanges<SL5_BOARD_DBCONTEXT>
    {
        //초기값을 넣어준다. - 초기값이 반영되도록 할려면 sdf파일을 삭제하고 다시 실행하면 반영된다.(수동으로 넣으셔도됨)
        protected override void Seed(SL5_BOARD_DBCONTEXT context)
        {
            var admin_member = new MST_MEMBER
            {
                EMAIL = "admin@localhost",
                PASSWORD = "p@ssw0rd",
                MEMBER_NAME = "관리자",
                NICK_NAME = "관리자",
                DESCRIPTION = "사이트 관리하는 슈퍼 유저",
                MEMBER_SCORE = 1000,
                MEMBER_GRADE = "Z",
                STATUS = 1,
                REG_DT = DateTime.Now,
                REG_IDX = 1
            };
            context.MST_MEMBERS.Add(admin_member);
            context.SaveChanges();

            var test_board = new BOARD_MAIN
            {
                BOARD_NAME = "Kakisoft MainPage",
                BOARD_DESC = "Kakisoft Main 페이지 입니다.",
                BOARD_TYPE = "NOR",
                MEMBER_CHECK = false,
                BOARD_STATE = "02",
                BOARD_ORDER = 1,
                REG_DT = DateTime.Now,
                REG_IDX = 1
            };
            context.BOARD_MAINS.Add(test_board);
            context.SaveChanges();
        }
    }
}

2. SL5_BOARDDomainService.cs

MST_MEMBER 일부

#region MST_MEMBER
public IQueryable<MST_MEMBER> GetMstMember()
{
 return this.DbContext.MST_MEMBERS;
}

//이메일과 패스워드를 가지고 로그인 정보 조회 - 나중을 위해서 미리 만들어 놓은 것
public IQueryable<MST_MEMBER> GetMstMemberByLogin(string Email, string Password)
{
 var query = from m in this.DbContext.MST_MEMBERS
    where m.EMAIL == Email && m.PASSWORD == Password
    select m;
 return query.AsQueryable();
}

    //조건을 입력 받아서 해당 해당 레코드만 반환한다.
    public IQueryable<MST_MEMBER> GetMstMemberByCondition(string Condition)
    {
        var query = from m in this.DbContext.MST_MEMBERS
                    where m.MEMBER_NAME.Contains(Condition)
                          || m.EMAIL.Contains(Condition)
                          || m.NICK_NAME.Contains(Condition)
                    select m;

        return query.AsQueryable();
    }

public void InsertBoardList(MST_MEMBER entity)
{
 DbEntityEntry<MST_MEMBER> entityEntry = this.DbContext.Entry(entity);
 if ((entityEntry.State != EntityState.Detached))
 {
  entityEntry.State = EntityState.Added;
 }
 else
 {
  this.DbContext.MST_MEMBERS.Add(entity);
 }
}

public void UpdateBoardList(MST_MEMBER entity)
{
 this.DbContext.MST_MEMBERS.AttachAsModified(entity, this.ChangeSet.GetOriginal(entity), this.DbContext);
}

public void DeleteBoardList(MST_MEMBER entity)
{
 DbEntityEntry<MST_MEMBER> entityEntry = this.DbContext.Entry(entity);
 if ((entityEntry.State != EntityState.Deleted))
 {
  entityEntry.State = EntityState.Deleted;
 }
 else
 {
  this.DbContext.MST_MEMBERS.Attach(entity);
  this.DbContext.MST_MEMBERS.Remove(entity);
 }
}
#endregion

3. SL5_BOARD 프로젝트

Models폴더에 MstMember.cs 추가

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.ServiceModel.DomainServices.Client;
using SL5_BOARD.Resources;
using SL5_BOARD.Web.Model;
using System.Linq;

namespace SL5_BOARD.Models
{
    [Export(typeof(MstMember))]
    public class MstMember : INotifyPropertyChanged
    {
        #region PropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

        [Import(typeof(BoardContext))]
        public BoardContext BoardDomainService { get; set; }

        //너무 길어서 짧게 쓸려고 만들어 놓은것 - 모든 데이터를 가진다.
        public EntitySet<MST_MEMBER> CollectionData
        {
            get
            {
                return BoardDomainService.Context.MST_MEMBERs;
            }
        }

        //현재 선택되어있는 회원
        MST_MEMBER currentData;

        public MST_MEMBER CurrentData
        {
            get
            {
                return currentData;
            }
            set
            {
                if (currentData != value)
                {
                    currentData = value;
                    OnPropertyChanged("CurrentData");
                }
            }
        }

        //조건조회시 사용되는 데이터
        ObservableCollection<MST_MEMBER> conditionData;

        public ObservableCollection<MST_MEMBER> ConditionData
        {
            get
            {
                return conditionData;
            }
            set
            {
                if (conditionData != value)
                {
                    conditionData = value;
                    OnPropertyChanged("ConditionData");
                }
            }
        }

        //메시지 출력용
        string messageData;

        public string MessageData
        {
            get
            {
                return messageData;
            }
            private set
            {
                if (messageData != value)
                {
                    messageData = "[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] " + value;
                    OnPropertyChanged("MessageData");
                }
            }
        }

        //최대 회원수
        int maxCount;

        public int MaxCount
        {
            get
            {
                return maxCount;
            }
            private set
            {
                if (maxCount != value)
                {
                    maxCount = value;
                    OnPropertyChanged("MaxCount");
                }
            }
        }

        //생성자
        public MstMember()
        {
            this.PropertyChanged += new PropertyChangedEventHandler(MstMember_PropertyChanged);
        }

        //현재 모델에서 발생한 프로퍼티체인지 이벤트 처리 - 다른 프로퍼티 변경작업시 사용
        void MstMember_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
            }
        }

        //전체 회원 조회
        public void Getting()
        {
            BoardDomainService.Context.GetMstMemberQuery().IncludeTotalCount = true;
            //Load가 완료되면 loadComplete 함수로 콜백됨
            BoardDomainService.Context.Load(
                                            BoardDomainService.Context.GetMstMemberQuery(),
                                            LoadBehavior.RefreshCurrent,
                                            load =>
                                            {
                                                loadComplete(load);
                                            },
                                            true);
        }

        //조건으로 회원 조회
        public void Getting(string Condition)
        {
            if (Condition.Length > 0)
            {
                //조건이 있을 때는 이걸 호출하고
                BoardDomainService.Context.Load(
                                                BoardDomainService.Context.GetMstMemberByConditionQuery(Condition),
                                                LoadBehavior.RefreshCurrent,
                                                load =>
                                                {
                                                    loadComplete(load);
                                                },
                                                true);
            }
            else
            {
                //조건이 없을 대는 그냥 전체 호출
                Getting();
            }
        }

        //로그인
        public void Login(string email, string password)
        {
            //로그인시 이메일과 비밀번호를 쿼리에 넣어서 보내야함.(쿼리도 바꿔주고)
            if (BoardDomainService.Context.IsLoading == false)
            {
                BoardDomainService.Context.Load(
                                                BoardDomainService.Context.GetMstMemberQuery(),
                                                LoadBehavior.RefreshCurrent,
                                                load =>
                                                {
                                                    loadComplete(load);
                                                },
                                                true);
            }
            else
            {
                MessageData = "조회 중이니 잠시 후 다시 시도해 주시기 바랍니다.";
            }
        }

        //로드 완료 콜벡 함수
        protected void loadComplete(LoadOperation lo)
        {
            if (lo.HasError == false)
            {
                //작업 성공
                if (lo.TotalEntityCount > 0)
                {
                    MessageData = "작업 성공";

                    //ConditionData를 만드는 방법 2가지
                    //1번방법 : LINQ 사용
                    //반환된 모든 엔티티를 캐스트 하는 것을 쿼리로 만들고
                    var query = from l in lo.AllEntities.Cast<MST_MEMBER>()
                                select l;
                    //컨디션 데이터를 클리어하고
                    if (ConditionData == null)
                        ConditionData = new ObservableCollection<MST_MEMBER>();
                    ConditionData.Clear();
                    //쿼리를 리스트로 변경하고, 컨데션 데이터에 하나씩 끄집어 넣음
                    query.ToList().ForEach(p => ConditionData.Add(p));

                   
                    //2번방법 : 바로 옵져블컬렉션으로 만들기
                    //한줄로 간단하게
                    //ConditionData = new ObservableCollection<MST_MEMBER>(lo.AllEntities.Cast<MST_MEMBER>());


                    //여긴 반환된 쿼리 이름을 가지고 쿼리마다 뭔가 처리를 할 수 있음
                    //switch (lo.EntityQuery.QueryName)
                    //{
                    //    case "GetMstMember":
                    //        break;
                    //    case "GetMstMemberByConditionQuery":
                    //        break;
                    //    default:
                    //        break;
                    //}
                }
                else
                {
                    ConditionData = null;
                }
            }
            else
            {
                //실행했던 쿼리 이름을 가지고 결과 처리
                MessageData = "작업 실패";
                switch (lo.EntityQuery.QueryName)
                {
                    case "GetMstMemberByConditionQuery":
                        break;
                    default:
                        //로그인 실패
                        MessageData = "메일 주소와 비밀번호를 확인해 주시기 바랍니다.";
                        break;
                }
            }

        }

        //회원추가
        public void Adding(MST_MEMBER mm)
        {
            if (mm != null && mm is MST_MEMBER)
            {
                mm.REG_DT = DateTime.Now;
                mm.REG_IDX = 1;
                BoardDomainService.Context.MST_MEMBERs.Add(mm);
                CurrentData = mm;
            }
            else
            {
                MessageData = "데이터가 없거나 형식이 다릅니다.";
            }
        }

        //회원삭제
        public void Removing(MST_MEMBER mm)
        {
            if (mm != null && mm is MST_MEMBER)
            {
                BoardDomainService.Context.MST_MEMBERs.Remove(mm);
            }
            else
            {
                MessageData = "데이터가 없거나 형식이 다릅니다.";
            }
        }

        //회원 정보 저장
        public void Saving()
        {
            BoardDomainService.Context.SubmitChanges(submitCallBack, null);
        }

        //저장완료 콜백 함수
        private void submitCallBack(SubmitOperation so)
        {
            if (so.IsComplete == true)
            {
                MessageData = "작업을 완료 했습니다.";
            }
            else
            {
                MessageData = "작업을 실패 했습니다.";
            }
        }

        //수정 취소
        public void Canceling()
        {
            BoardDomainService.Context.RejectChanges();
        }
    }
}

4. MstMemberViewModel.cs

using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Windows.Input;
using Microsoft.Expression.Interactivity.Core;
using SL5_BOARD.Models;
using SL5_BOARD.Web.Model;
using System.Windows.Controls;

namespace SL5_BOARD.ViewModels
{
    [Export(typeof(MstMemberViewModel))]
    public class MstMemberViewModel : INotifyPropertyChanged
    {
        #region PropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

        //회원 마스터 모델 임포트
        [Import(typeof(MstMember))]
        public MstMember MstMemberData { get; set; }

        //생성자
        public MstMemberViewModel()
        {
        }

        //회원 전체 조회
        ICommand selectCommand;
        public ICommand SelectCommand
        {
            get
            {
                if (selectCommand == null)
                {
                    selectCommand = new ActionCommand(() =>
                    {
                        MstMemberData.Getting();
                    });
                }
                return selectCommand;
            }
        }
        public void Select()
        {
            MstMemberData.Getting();
        }

        //조건을 가지는 조회 커맨드
        ICommand selectConditionCommand;
        public ICommand SelectConditionCommand
        {
            get
            {
                if (selectConditionCommand == null)
                {
                    selectConditionCommand = new ActionCommand(condition =>
                    {
                        //조건을 가지는 회원 조회 함수 실행
                        MstMemberData.Getting(condition as string);
                    });
                }
                return selectConditionCommand;
            }
        }

        //회원 추가
        ICommand addCommand;
        public ICommand AddCommand
        {
            get
            {
                if (addCommand == null)
                {
                    addCommand = new ActionCommand(() =>
                    {
                        MST_MEMBER obj = new MST_MEMBER();
                        MstMemberData.Adding(obj);
                    });
                }
                return addCommand;
            }
        }

        //회원 삭제
        ICommand removeCommand;
        public ICommand RemoveCommand
        {
            get
            {
                if (removeCommand == null)
                {
                    removeCommand = new ActionCommand(p =>
                    {
                        if (p != null)
                        {
                            MST_MEMBER obj = p as MST_MEMBER;
                            MstMemberData.Removing(obj);
                        }
                    });
                }
                return removeCommand;
            }
        }

        //저장
        ICommand saveCommand;
        public ICommand SaveCommand
        {
            get
            {
                if (saveCommand == null)
                {
                    saveCommand = new ActionCommand(() =>
                    {
                        MstMemberData.Saving();
                    });
                }
                return saveCommand;
            }
        }

        //취소
        ICommand cancelCommand;
        public ICommand CancelCommand
        {
            get
            {
                if (cancelCommand == null)
                {
                    cancelCommand = new ActionCommand(() =>
                    {
                        MstMemberData.Canceling();
                    });
                }
                return cancelCommand;
            }
        }

        //회원 변경
        ICommand selectChanged;
        public ICommand SelectChangedCommand
        {
            get
            {
                if (selectChanged == null)
                {
                    selectChanged = new ActionCommand(para => SelectChangedOperation(para));
                }
                return selectChanged;
            }
        }
        private void SelectChangedOperation(object para)
        {
            MstMemberData.CurrentData = para as MST_MEMBER;
        }

        //로그인
        ICommand loginCommand;
        public ICommand LoginCommand
        {
            get
            {
                if (loginCommand == null)
                {
                    loginCommand = new ActionCommand(pnl => LoginOperation(pnl));
                }
                return loginCommand;
            }
        }

        //로그인시에 사용할 예정인데..아직 미완성
        private void LoginOperation(object pnl)
        {
            StackPanel sp = pnl as StackPanel;
            string email;
            string password;

            MstMemberData.Getting();
        }
    }
}

5. MstMemberView.xaml

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:SL5_BOARD_Converter="clr-namespace:SL5_BOARD.Converter"

xmlns:SL5_BOARD_ViewModels="clr-namespace:SL5_BOARD.ViewModels" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"

x:Name="userControl"

x:Class="SL5_BOARD.Views.MstMemberView"
    mc:Ignorable="d"
    d:DesignHeight="500" d:DesignWidth="600">
   
 <UserControl.Resources>

  <!--이 변환 함수는 없어도 상관없음..실패작-->
  <SL5_BOARD_Converter:ActualHeightToHeightConverter x:Key="ActualHeightToHeightConverter"/>
  <SL5_BOARD_Converter:ValueToBoolConverter x:Key="ValueToBoolConverter"/>
 </UserControl.Resources>
   
 <Grid x:Name="LayoutRoot" Background="White">
  <i:Interaction.Triggers>
   <i:EventTrigger>

    <!--로드되고 바로 데이터 뿌리기 위해-->
    <i:InvokeCommandAction Command="{Binding SelectCommand, Mode=OneWay}"/>
   </i:EventTrigger>
  </i:Interaction.Triggers>

  <Grid.ColumnDefinitions>
   <ColumnDefinition Width="192"/>
   <ColumnDefinition/>
  </Grid.ColumnDefinitions>

   <Grid.RowDefinitions>
   <RowDefinition Height="38"/>
   <RowDefinition Height="35"/>
   <RowDefinition Height="28"/>
   <RowDefinition/>
   <RowDefinition Height="35"/>
  </Grid.RowDefinitions>

  <Grid.DataContext>
   <SL5_BOARD_ViewModels:MstMemberViewModel/>
  </Grid.DataContext>

   <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="회원 관리" FontSize="14.667" FontWeight="Bold" Foreground="{StaticResource ForeColor_Blues}" Margin="10,6,0,10" d:LayoutOverrides="Height"/>
  <Path Data="M0,30 L400,30" Fill="#FF5181B9" Height="3" Margin="0" Stretch="Fill" Stroke="#FF5181B9" UseLayoutRounding="False" VerticalAlignment="Bottom" StrokeLineJoin="Bevel" StrokeThickness="3" Grid.ColumnSpan="2"/>
  <Button x:Name="btnSelect" Content="조회" HorizontalAlignment="Right" Margin="0,6,8,7" Width="70" Grid.Column="1" Grid.Row="1" Height="22" Command="{Binding SelectCommand, Mode=OneWay}" IsEnabled="False"/>
  <Path Data="M0,30 L400,30" Fill="{StaticResource ForeColor_Kakis}" Stretch="Fill" Stroke="{StaticResource ForeColor_Kakis}" UseLayoutRounding="False" StrokeLineJoin="Bevel" StrokeThickness="3" Grid.ColumnSpan="2" Grid.Row="1" Height="3" VerticalAlignment="Bottom" Margin="0,0,0,-1"/>
  <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="회원 목록" FontSize="12" FontWeight="Bold" Foreground="{StaticResource ForeColor_Kakis}" Margin="10,7,0,3" Grid.Row="2" d:LayoutOverrides="Height"/>


  <ListBox x:Name="lbBoardMain" Margin="8,30,8,8" Grid.Row="3" BorderBrush="{x:Null}" ItemsSource="{Binding MstMemberData.ConditionData}" ItemTemplate="{StaticResource MemberDataTemplate}">
   <i:Interaction.Triggers>
    <i:EventTrigger EventName="SelectionChanged">
     <i:InvokeCommandAction Command="{Binding SelectChangedCommand, Mode=OneWay}" CommandParameter="{Binding SelectedItem, ElementName=lbBoardMain}"/>
    </i:EventTrigger>
   </i:Interaction.Triggers>
  </ListBox>


  <TextBlock TextWrapping="Wrap" Text="회원 상세 정보" FontSize="12" FontWeight="Bold" Foreground="{StaticResource ForeColor_Kakis}" Margin="8,8,0,2" Grid.Row="2" HorizontalAlignment="Left" Grid.Column="1" d:LayoutOverrides="Height"/>
  <Button x:Name="btnSearch" Content="조회" Margin="0,2,8,0" Grid.Row="3" VerticalAlignment="Top" HorizontalAlignment="Right" Width="70" Height="22" Command="{Binding SelectConditionCommand, Mode=OneWay}" CommandParameter="{Binding Text, ElementName=textBox}"/>
  <Button x:Name="btnSave" Content="저장" Margin="0,3,82,3" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Right" Width="70">
   <i:Interaction.Triggers>
    <i:EventTrigger EventName="Click">
     <i:InvokeCommandAction Command="{Binding SaveCommand, Mode=OneWay}"/>
    </i:EventTrigger>
   </i:Interaction.Triggers>
  </Button>

   <Border x:Name="border" Grid.Column="1" Grid.Row="3" BorderBrush="Black" BorderThickness="2" Margin="0" >
   <ScrollViewer Height="{Binding ActualHeight, ElementName=border, Mode=OneWay}" >
    <StackPanel x:Name="spMstMemberInfo" DataContext="{Binding MstMemberData.CurrentData}" ScrollViewer.VerticalScrollBarVisibility="Visible" Margin="2" Height="420">
     <StackPanel Orientation="Horizontal" Height="28">
      <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
      <TextBlock TextWrapping="Wrap" Text="메일주소" FontSize="12" VerticalAlignment="Center" Width="90" Padding="5,0,0,0"/>
      <TextBox TextWrapping="Wrap" Text="{Binding EMAIL, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}" IsReadOnly="True"/>
     </StackPanel>
     <StackPanel Orientation="Horizontal" Height="28">
      <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
      <TextBlock TextWrapping="Wrap" Text="회원명" FontSize="12" VerticalAlignment="Center" Width="90" Padding="5,0,0,0"/>
      <TextBox TextWrapping="Wrap" Text="{Binding MEMBER_NAME, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}" IsReadOnly="True"/>
     </StackPanel>
     <StackPanel Orientation="Horizontal" Height="28">
      <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
      <TextBlock TextWrapping="Wrap" FontSize="12" VerticalAlignment="Center" Width="90" Padding="5,0,0,0" Text="별칭"/>
      <TextBox TextWrapping="Wrap" Text="{Binding NICK_NAME, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}" IsReadOnly="True"/>
     </StackPanel>
     <StackPanel Orientation="Horizontal" Height="28">
      <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
      <TextBlock TextWrapping="Wrap" Text="메일링가입" FontSize="12" VerticalAlignment="Center" Width="90" Padding="5,0,0,0"/>
      <CheckBox Content="{Binding EMAIL_RECEIVE}" VerticalAlignment="Center" IsChecked="{Binding EMAIL_RECEIVE, Mode=TwoWay}" IsHitTestVisible="False"/>
     </StackPanel>
     <StackPanel Orientation="Horizontal" Height="28" >
      <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
      <TextBlock TextWrapping="Wrap" Text="소개" FontSize="12" VerticalAlignment="Center" Width="90" Padding="5,0,0,0"/>
      <TextBox TextWrapping="Wrap" Text="{Binding DESCRIPTION, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}" IsReadOnly="True" AcceptsReturn="True"/>
     </StackPanel>
     <StackPanel Orientation="Horizontal" Height="28">
      <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
      <TextBlock TextWrapping="Wrap" Text="마지막로그인" FontSize="12" VerticalAlignment="Center" Width="90" Padding="5,0,0,0"/>
      <TextBox TextWrapping="Wrap" Text="{Binding LAST_LOGIN_DT, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}" IsReadOnly="True"/>
     </StackPanel>
     <StackPanel Orientation="Horizontal" Height="28">
      <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
      <TextBlock TextWrapping="Wrap" Text="직업" FontSize="12" VerticalAlignment="Center" Width="90" Padding="5,0,0,0"/>
      <TextBox TextWrapping="Wrap" Text="{Binding JOB, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}" IsReadOnly="True"/>
     </StackPanel>
     <StackPanel Orientation="Horizontal" Height="28">
      <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
      <TextBlock TextWrapping="Wrap" Text="취미" FontSize="12" VerticalAlignment="Center" Width="90" Padding="5,0,0,0"/>
      <TextBox TextWrapping="Wrap" Text="{Binding HOBBY, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}" IsReadOnly="True"/>
     </StackPanel>
     <StackPanel Orientation="Horizontal" Height="28">
      <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
      <TextBlock TextWrapping="Wrap" Text="관심분야" FontSize="12" VerticalAlignment="Center" Width="90" Padding="5,0,0,0"/>
      <TextBox TextWrapping="Wrap" Text="{Binding INTEREST, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}" IsReadOnly="True"/>
     </StackPanel>
     <StackPanel Orientation="Horizontal" Height="28">
      <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
      <TextBlock TextWrapping="Wrap" Text="회원점수" FontSize="12" VerticalAlignment="Center" Width="90" Padding="5,0,0,0"/>
      <TextBox TextWrapping="Wrap" Text="{Binding MEMBER_SCORE, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}" IsReadOnly="True"/>
     </StackPanel>
     <StackPanel Orientation="Horizontal" Height="28">
      <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
      <TextBlock TextWrapping="Wrap" Text="회원등급" FontSize="12" VerticalAlignment="Center" Width="90" Padding="5,0,0,0"/>
      <TextBox TextWrapping="Wrap" Text="{Binding MEMBER_GRADE, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}" IsReadOnly="True"/>
     </StackPanel>
     <StackPanel Orientation="Horizontal" Height="28">
      <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
      <TextBlock TextWrapping="Wrap" Text="비고사항" FontSize="12" VerticalAlignment="Center" Width="90" Padding="5,0,0,0"/>
      <TextBox TextWrapping="Wrap" Text="{Binding ETC, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}" IsReadOnly="True"/>
     </StackPanel>
     <StackPanel Orientation="Horizontal" Height="28">
      <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
      <TextBlock TextWrapping="Wrap" Text="상태" FontSize="12" VerticalAlignment="Center" Width="90" Padding="5,0,0,0"/>
      <TextBox TextWrapping="Wrap" Text="{Binding STATUS, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}" IsReadOnly="True"/>
     </StackPanel>
     <StackPanel Orientation="Horizontal" Height="28">
      <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
      <TextBlock TextWrapping="Wrap" Text="등록일시" FontSize="12" VerticalAlignment="Center" Width="90" Padding="5,0,0,0"/>
      <TextBox TextWrapping="Wrap" Text="{Binding REG_DT, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}" IsReadOnly="True"/>
     </StackPanel>
     <StackPanel Orientation="Horizontal" Height="28">
      <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
      <TextBlock TextWrapping="Wrap" Text="수정일시" FontSize="12" VerticalAlignment="Center" Width="90" Padding="5,0,0,0"/>
      <TextBox TextWrapping="Wrap" Text="{Binding UPT_DT, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}" IsReadOnly="True"/>
     </StackPanel>
    </StackPanel>
   </ScrollViewer>
  </Border>

  <Button x:Name="btnCancel" Content="취소" Margin="0,3,8,3" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Right" Width="70">
   <i:Interaction.Triggers>
    <i:EventTrigger EventName="Click">
     <i:InvokeCommandAction Command="{Binding CancelCommand, Mode=OneWay}"/>
    </i:EventTrigger>
   </i:Interaction.Triggers>
  </Button>

  <TextBlock Margin="8,6,82,8" Grid.Row="1" TextWrapping="Wrap" Text="{Binding MstMemberData.MessageData, Mode=OneWay}" Grid.ColumnSpan="2" VerticalAlignment="Center" FontSize="12" Height="18"/>


  <TextBox x:Name="textBox" TextWrapping="Wrap" Text="{Binding BOARD_NAME, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Top" Style="{StaticResource NormalTextBoxStyle}" Margin="10,1,82,0" Grid.Row="3"/>

 </Grid>
</UserControl>

6. MstMemberView.xaml.cs

using System.ComponentModel.Composition;
using System.Windows.Controls;
using SL5_BOARD.ViewModels;

namespace SL5_BOARD.Views
{
    public partial class MstMemberView : UserControl
    {
        [Import(typeof(MstMemberViewModel))]
        public MstMemberViewModel MstMemberVM { get; set; }

        public MstMemberView()
        {
            InitializeComponent();

            CompositionInitializer.SatisfyImports(this);

            LayoutRoot.DataContext = MstMemberVM;
        }
    }
}

7.  MainPage.cs

using System;
using System.Linq;
using System.ServiceModel.DomainServices.Client;
using System.Windows;
using System.Windows.Controls;

using SL5_BOARD.Views;
using SL5_BOARD.ViewModels;
using System.ComponentModel.Composition;

namespace SL5_BOARD
{
    public partial class MainPage : UserControl
    {
        //메인 페이지 뷰모델 임포트
        [Import(typeof(MainPageViewModel))]
        public MainPageViewModel MainPageVM { get; set; }

        public MainPage()
        {
            InitializeComponent();

            //보드메인뷰는 아직은 그냥 마구잡이 생성해서 붙여 놓음
            //UserControl uc = new BoardMainView();
            UserControl uc = new MstMemberView();
            IcMain.Items.Add(uc);

 

            //임포트 초기화 : 이명령은 뷰단에서 한번 실행되면, 그 하위 컴포넌트 들도 차례대로 인스턴스화 된다.
            CompositionInitializer.SatisfyImports(this);

            //메인페이지 뷰모델을 LayoutRoot의 DataContext에 바인딩
            LayoutRoot.DataContext = MainPageVM;
        }
    }
}

8. 엄청난 양의 소스인데..
음..이렇게 올렸는데두 불구하고 빠진 부분이 있을 것 같은..불길한 예감이..추가로 필요한 부분은 리플로 요청하도록 하면된다..이 곳에서 중요하게 보아야 할 부분은 조건 검색 후 처리 방법이다. 어떤 식으로 처리가 되고 있는지...쭈욱 따라가 보면서 내용을 확인해 보자.

추가

BoardResourceDictionary.xaml 에 추가

 <DataTemplate x:Key="MemberDataTemplate">
  <Grid>
   <Border MinHeight="22">
    <StackPanel Orientation="Horizontal">
     <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="6,0,0,0"/>
     <TextBlock TextWrapping="Wrap" VerticalAlignment="Center" Margin="6,0,0,0" Foreground="{StaticResource NormalForeColor}" Text="{Binding MEMBER_NAME}"/>
     <TextBlock TextWrapping="Wrap" VerticalAlignment="Center" Margin="6,0,0,0" Foreground="{StaticResource NormalForeColor}" Text="{Binding EMAIL}"/>
    </StackPanel>
   </Border>
  </Grid>
 </DataTemplate>

Posted by MVP kaki104

이번 회에는 간단하게 메인 화면에 메인 메뉴를 출력하는 부분만 보도록 하겠다.


1. ViewModel폴더에 MainPageViewModel.cs 파일 추가

using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Windows.Input;
using Microsoft.Expression.Interactivity.Core;
using SL5_BOARD.Models;
using SL5_BOARD.Web.Model;

namespace SL5_BOARD.ViewModels
{
    //메인 페이지 익스포트
    [Export(typeof(MainPageViewModel))]
    public class MainPageViewModel : INotifyPropertyChanged
    {
        #region PropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

        //보드메인 모델 임포트 - 보드메인 재활용~
        [Import(typeof(BoardMain))]
        public BoardMain BoardMainData { get; set; }

        //생성자
        public MainPageViewModel()
        {
        }

        //메인 메뉴 조회 - 만들어 놓기는 했는데 사용하지는 않았는데도 목록이 보임
        public void MenuSelect()
        {
            BoardMainData.Getting();
        }

        //메뉴 체인지 커맨드
        ICommand menuChanged;
        public ICommand menuChangedCommand
        {
            get
            {
                if (menuChanged == null)
                {
                    menuChanged = new ActionCommand(para => MenuChangedOperation(para));
                }
                return menuChanged;
            }
        }
        private void MenuChangedOperation(object para)
        {
            BoardMainData.CurrentData = para as BOARD_MAIN;
        }
    }
}

소스 자체는 특이한 사항은 없다. 단지 BoardMain.cs 모델을 재사용 하는 부분만을 보면 되겠다.

 2. MainPage.xaml.cs 수정

using System;
using System.Linq;
using System.ServiceModel.DomainServices.Client;
using System.Windows;
using System.Windows.Controls;
using SL5_BOARD.Views;
using SL5_BOARD.ViewModels;
using System.ComponentModel.Composition;

 namespace SL5_BOARD
{
    public partial class MainPage : UserControl
    {
        //메인 페이지 뷰모델 임포트
        [Import(typeof(MainPageViewModel))]
        public MainPageViewModel MainPageVM { get; set; }

        public MainPage()
        {
            InitializeComponent();

            //보드메인뷰는 아직은 그냥 마구잡이 생성해서 붙여 놓음
            UserControl uc = new BoardMainView();
            IcMain.Items.Add(uc);

            //임포트 초기화 : 이명령은 뷰단에서 한번 실행되면, 그 하위 컴포넌트 들도 차례대로 인스턴스화 된다.
            CompositionInitializer.SatisfyImports(this);

            //메인페이지 뷰모델을 LayoutRoot의 DataContext에 바인딩
            LayoutRoot.DataContext = MainPageVM;
        }
    }
}

3. MainPage.xaml 수정

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
    xmlns:SL5_BOARD_ViewModels="clr-namespace:SL5_BOARD.ViewModels" x:Class="SL5_BOARD.MainPage"
    mc:Ignorable="d"
    d:DesignHeight="500" d:DesignWidth="600">

    <Grid x:Name="LayoutRoot" Background="White">
     <Grid.RowDefinitions>
      <RowDefinition Height="6*"/>
      <RowDefinition Height="20*"/>
      <RowDefinition Height="68*"/>
      <RowDefinition Height="6*"/>
     </Grid.RowDefinitions>
        <!--메인페이지 뷰모델 바인딩 : 블랜드에서 바인딩을 쉽게 하기 위해서 넣어놓은 것임 -->
     <Grid.DataContext>
      <SL5_BOARD_ViewModels:MainPageViewModel/>
     </Grid.DataContext>

     <Button x:Name="btnBoardMain" Content="게시판관리" Width="70" Height="22" HorizontalAlignment="Left" Margin="5,4,0,4"/>
     <Button x:Name="btnMstMember" HorizontalAlignment="Left" Margin="79,4,0,4" Width="70" Height="22" Content="회원 관리"/>
     <Button x:Name="btnLogin" Content="Login" HorizontalAlignment="Right" Margin="0,4,6,4" Width="70" Height="22"/>
        <!--메인 이미지 영역-->
     <Border BorderBrush="Black" BorderThickness="1" Grid.Row="1">
      <Border.Background>
       <LinearGradientBrush EndPoint="0.53,0.953" StartPoint="0.528,0.04">
        <GradientStop Color="#FF16BDA3" Offset="0"/>
        <GradientStop Color="#FF040E0D" Offset="1"/>
        <GradientStop Color="#FF94E0D4" Offset="0.443"/>
       </LinearGradientBrush>
      </Border.Background>
           
      <Viewbox Stretch="Fill" Margin="6,0" HorizontalAlignment="Center" VerticalAlignment="Center" MaxWidth="955" MaxHeight="228">
       <Image x:Name="ImgLogo" Source="Images/logoPixelImage.jpg" Stretch="None" HorizontalAlignment="Center" VerticalAlignment="Center"/>
      </Viewbox>
     </Border>

        <!--컨텐츠 영역 그리드-->
     <Grid x:Name="grdContent" Grid.Row="2" Margin="6,6,6,0">
      <Grid.RowDefinitions>
       <RowDefinition Height="163"/>
       <RowDefinition/>
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
       <ColumnDefinition Width="153"/>
       <ColumnDefinition/>
      </Grid.ColumnDefinitions>
      <sdk:GridSplitter HorizontalAlignment="Left" Width="8" Grid.Column="1" Grid.RowSpan="2"/>

            <!--로그인 멤버 정보 보이는 박스 영역-->
      <Border x:Name="MemberInfo" BorderBrush="#FF81C1C7" BorderThickness="4" CornerRadius="10">
       <StackPanel>
        <TextBlock TextWrapping="Wrap" Text="나의 활동" FontSize="13.333" FontWeight="Bold" HorizontalAlignment="Left" Foreground="#FF3CA4AD" Margin="5,0"/>
        <StackPanel Margin="4,5,4,0" Orientation="Horizontal">
         <TextBlock TextWrapping="Wrap" Text="이름" FontSize="12" Width="40"/>
         <TextBlock TextWrapping="Wrap" Text="카키104" FontSize="12" FontWeight="Bold" Margin="5,0,0,0"/>
         <TextBlock TextWrapping="Wrap" Text="님" FontSize="12" Margin="4,0,0,0"/>
        </StackPanel>
        <StackPanel Margin="4,0" Orientation="Horizontal">
         <TextBlock TextWrapping="Wrap" Text="등급" FontSize="12" Width="40"/>
         <TextBlock TextWrapping="Wrap" Text="관리자" FontSize="12" Margin="5,0,0,0"/>
        </StackPanel>
        <StackPanel Margin="4,0" Orientation="Horizontal">
         <TextBlock TextWrapping="Wrap" Text="게시물" FontSize="12" Width="40"/>
         <TextBlock TextWrapping="Wrap" Text="44" FontSize="12" FontWeight="Bold" Margin="5,0,0,0" Foreground="{StaticResource CountColor}"/>
        </StackPanel>
        <StackPanel Margin="4,0" Orientation="Horizontal">
         <TextBlock TextWrapping="Wrap" Text="덧글" FontSize="12" Width="40"/>
         <TextBlock TextWrapping="Wrap" FontSize="12" FontWeight="Bold" Margin="5,0,0,0" Foreground="{StaticResource CountColor}" Text="41"/>
        </StackPanel>
        <StackPanel Margin="4,0" Orientation="Horizontal">
         <TextBlock TextWrapping="Wrap" Text="로그인" FontSize="12" Width="40"/>
         <TextBlock TextWrapping="Wrap" Text="2011.07.12" FontSize="12" Margin="5,0,0,0"/>
        </StackPanel>
        <Button Content="글쓰기" Height="30" FontSize="14.667" FontWeight="Bold" Width="120" Margin="0,4"/>
       </StackPanel>
      </Border>

            <!--메인 메뉴 보이는 영역-->
      <Border x:Name="MainMenu" BorderBrush="#FFAED8DC" BorderThickness="4" Grid.Row="1" Margin="0,8,0,0" CornerRadius="10" Padding="6">
       <ScrollViewer BorderThickness="0" ScrollViewer.VerticalScrollBarVisibility="Auto">
        <StackPanel Margin="0">
        <!--ItemSource : ListBox의 DataContext가 상위의 LayoutRoot가 가지는 MainPageViewModel을 상속

                                     받았기 때문에 그 안에 존재하는 BoardMainData.CollectionData 프로퍼티를 넣는다.
               ItemTemplate : BoardMainView에서 사용했던 템플릿을 그대로 사용
               ScrollViewer.HorizontalScrollBarVisibility="Disabled" : ListBoxItem의 길이가 길어서 넘어가더라도

                                     가로 스크롤바를 만들지 않음-->
         <ListBox BorderThickness="0"
                                 ItemsSource="{Binding Path=BoardMainData.CollectionData, Mode=OneWay}"
                                 ItemTemplate="{StaticResource BoardMainDataTemplate}"
                                 ScrollViewer.HorizontalScrollBarVisibility="Disabled"/>
        </StackPanel>
       </ScrollViewer>
      </Border>
      <ItemsControl x:Name="IcMain" Grid.Column="1" Grid.RowSpan="2" Margin="10,0,0,0"/>
     </Grid>
    </Grid>
</UserControl>

4. 이렇게 만들어 놓은 것의 장점을 보도록 하자,
일단 추가 버튼을 눌러서 게시판을 하나 추가해보자


추가 버튼을 누르자 마자 메인 메뉴에도 동을한 내용이 추가되어 바로 보인다..즉 BoardMain 모델에 데이터가 BoardMainViewModel과 MainPageViewModel에서 동일하게 사용된다는 것이다. 수정하면 수정하는 대로 바로 바로 적용이 된다. 음음.. 이쯤에서 이런 내용이 의미하는 바를 느끼면 좋은데.. 간단한 내용이니 바로 작업해서 내용을 확인해 보면 된다.

Posted by MVP kaki104

생각보다 시간도 오래걸렸고, 내용도 많아서, 2회에 걸쳐서 진행 하려고 한다. 일단 이번회에는 Model, ViewModel, View 구성에 관한 내용이다. 일단 완성된 화면을 보자..그래야..앞으로 갈길이 멀구나~라는 느낌이 온다.

어떻게 시작을 관리자 페이지에서 사용할 넘을 먼저 만들어서..화면 구성이 머 그냥 그렇다. 역시나 디자이너가 필요해..쿨럭

1. 클라이언트의 전체 적인 레이아웃
1-1. MEF(Manage Extensibility Framework)에 대해 먼저 알아 보고 넘어가야한다. 그동안 다른 강좌에서 MEF 사용에 대해서 이야기를 좀 했었다..음..물론 대충 어떻게 쓰는 거다 정도였지만..그래서 이번에는 좀더 자세하게 한번 다루고 넘어가려고 한다. 왜냐하면 이번 클라이언트는 MEF를 이용한 Export/Import가 다수 존재 하기 때문에 개념이 쉽게 잡히지 않을 수 있기 때문이다.

참고페이지(이 페이지는 꼭 읽어 볼 것을 권한다.)
http://www.c-sharpcorner.com/UploadFile/shivprasadk/3122/

우리가 만든 뷰, 뷰모델, 모델 등은 모두 Export 대상이 될 수 있고 그 Export한 내용들은 Composition Container라는 곳에 모여서 사용되어 지기를 기다라고 있다. 그럼 우린 그 중 필요한 내용들 만 골라서 Import라는 명령을 이용해서 건물을 만들수도, 차를 만들수도 있는 것이다. 그럼 이러한 MEF를 사용 했을때 좋은 점은 무엇인가..

1) 가장 큰 장점은 Decoupled하게 프로그램을 개발 할 수 있다.
Export 대상이 현재 프로젝트 어디 있던지, 아니면 현재 프로젝트가 아니라 웹에 이미 배포가 되어있는 것이라도..Import를 해서 끌어다가 사용 할 수 있으며, Export된 내용이 변경되더라도 Import 한 곳에는 아무런 영향을 미치지 않는다.

2) 하나의 인스턴스를 공유 한다.
하나의 모델이 A라는 aaa뷰에서 Import되고, bbb뷰모델에서 Import되고, ccc모델에서 Import되었을때 3곳에서 사용하는 A라는 Import된 모델은 동일한 내용이다.

3) 실행 중에도 내용이 변경될 수 있다.

등의 여러가지 장점들이 있다...그렇다면 이번에는 프로젝트에서 사용한 내용에 대해서 보도록 하자.


2. SL5_BOARD 프로젝의 전체 구성

2-1. 왜 이런 구성을 가지는가??
우선 BoardContext.cs는 서버와 통신을 통해서 데이터를 조회하고, 저장하는 SL5_BOARDDomainContext가 존재하며, 이 것을 통해서 가지고 온 데이터를 컨텍스트로서 로컬에서 관리를 할 수 있도록 하기위해서이다. 

모델 클래스는 BoardContext를 모두 Import를 받아서 하나의 로컬 저장소의 데이터를 모델 별로 관리를 할 수 있도록 만들어 놓은 것이고, 뷰모델은 뷰에서 사용할 ICommand와 비지니스 로직을 관리하며, 뷰는 사용자와의 인터렉션을 할 수 있도록 나누어 놓은 것이다..

최종적으로 모든 뷰는 ImportMany라는 명령을 통해서 MainPage.xaml에 Import되어 사용 되어 질 것이다.

이런 구성을 만들기 위해 나름 고민을 만이 했기 때문에..음음..아주 최고의 모델은 아니라도 마구 만들어진 모델은 아니니 나중에라도 이런 구성은 가지고 가서 사용해도 괜찮을 것이다. 참고로 이렇게 만들어 놓아야 진정한 Model-View-ViewModel이 되는 것이다..(처음부터 이렇게 만들면 개발 시간이 막코딩하는 것보다 시간이 2배 정도는 더 든다;;;..그러나 나중에 유지 보수하는 시간 까지 놓고 전체 적으로 본다면 이런 개발 패턴이 훨씬 유지보수하는데 효율적이다.)

2-2. SL5_BOARD 프로젝트에 리퍼런스 추가

Add Reference

Microsoft.Expression.Interactions 5.0.5.0
System.ComponentModel.Composition 5.0.5.0
System.ComponentModel.Composition.Initialization 5.0.5.0
System.Windows.Interactivity 5.0.5.0

3. Models

BoardContext.cs

//이 모델은 아~무것도 없다..그냥 통신하기 위한 컨텍스트만 하나 달랑있다~

using System.ComponentModel;
using System.ComponentModel.Composition;
using SL5_BOARD.Web.DomainService;

namespace SL5_BOARD.Models
{
    //Export한다~
    [Export(typeof(BoardContext))]
    public class BoardContext : INotifyPropertyChanged
    {
        SL5_BOARDDomainContext context;
        public SL5_BOARDDomainContext Context
        {
            get
            {
                return context;
            }

            private set
            {
                if (context != null)
                {
                    context = value;
                    OnPropertyChanged("Context");
                }
            }
        }

        #region PropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

        public BoardContext()
        {
            context = new SL5_BOARDDomainContext();
        }
    }
}

BoardMain.cs

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.Composition;
using System.ServiceModel.DomainServices.Client;
using SL5_BOARD.Resources;
using SL5_BOARD.Web.Model;


namespace SL5_BOARD.Models
{
    //BoardMain 모델을 익스포트 시킨다.
    [Export(typeof(BoardMain))]
    public class BoardMain : INotifyPropertyChanged
    {
        //컨텍스트를 임포트 시킨다
        [Import(typeof(BoardContext))]
        public BoardContext BoardDomainService { get; set; }

        //너무 길어서 짧게 쓸려고 만들어 놓은것 - 뷰단에서 이걸 바인딩 한다.
        public EntitySet<BOARD_MAIN> CollectionData
        {
            get
            {
                return BoardDomainService.Context.BOARD_MAINs;
            }
        }

        //게시판 종류 목록 바인딩 하기 위해
        public ObservableCollection<CBoardType> BoardTypeCol { get; set; }

        //게시판 상태 바인딩용 - 이게 잘 앙대는 것 중에 하나인데;; 라디오버튼 앞으로 사용하지 말아야할듯 ㅋ
        //별것두 아닌데..바인딩으로 처리할려니 할일이 대폭 늘어난다;;
        string boardStateText;
        public string BoardStateText
        {
            get
            {
                return boardStateText;
            }
            set
            {
                if (boardStateText != value)
                {
                    boardStateText = value;

                    switch (boardStateText)
                    {
                        case "준비중":      //준비중
                            CurrentData.BOARD_STATE = "01";
                            break;
                        case "사용중":      //사용중
                            CurrentData.BOARD_STATE = "02";
                            break;
                        case "일시중지":    //일시중지
                            CurrentData.BOARD_STATE = "03";
                            break;
                        case "삭제":        //삭제
                            CurrentData.BOARD_STATE = "04";
                            break;
                    }
                    OnPropertyChanged("BoardStateText");
                }
            }
        }

        //현재 선택되어있는 보드메인
        BOARD_MAIN currentData;
        public BOARD_MAIN CurrentData
        {
            get
            {
                return currentData;
            }
            set
            {
                if (currentData != value)
                {
                    currentData = value;
                    OnPropertyChanged("CurrentData");
                }
            }
        }

        //각종 메시지 출력용
        string messageData;
        public string MessageData
        {
            get
            {
                return messageData;
            }
            private set
            {
                if (messageData != value)
                {
                    messageData = "[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "] " + value;
                    OnPropertyChanged("MessageData");
                }
            }
        }

        //만들어 놓기는 했는데..아직 사용은 앙대고 있다. 불러온 카운트를 넣어서 사용할 예정
        int maxCount;
        public int MaxCount
        {
            get
            {
                return maxCount;
            }
            private set
            {
                if (maxCount != value)
                {
                    maxCount = value;
                    OnPropertyChanged("MaxCount");
                }
            }
        }

        #region PropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

        //생성자
        public BoardMain()
        {
            //게시판 종류 설정
            BoardTypeCol = new ObservableCollection<CBoardType>
            {
                new CBoardType{BoardType = "NOR", BoardTypeComboText="일반게시판"},
                new CBoardType{BoardType = "PIC", BoardTypeComboText="사진게시판"},
                new CBoardType{BoardType = "ETC", BoardTypeComboText="기타게시판"}
            };
            this.PropertyChanged += new PropertyChangedEventHandler(BoardMain_PropertyChanged);
        }

        //자기 자신이 프로퍼티 체인지 이벤트 발생시 연속적인 다른 프로퍼티를 고치는 용도
       void BoardMain_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "CurrentData":
                    if (CurrentData != null)
                    {

                        //음..라디오 박스 때문에 신경썼는데..처음에만 대구 나중에는 잘 앙된다..원인은 불명
                        switch (CurrentData.BOARD_STATE)
                        {
                            case "01":      //준비중
                                BoardStateText = "준비중";
                                break;
                            case "02":      //사용중
                                BoardStateText = "사용중";
                                break;
                            case "03":      //일시중지
                                BoardStateText = "일시중지";
                                break;
                            case "04":      //삭제
                                BoardStateText = "삭제";
                                break;
                        }
                    }
                    else
                    {
                        BoardStateText = "";
                    }
                    break;
            }
        }

        //데이터 조회
        public void Getting()
        {
            if (BoardDomainService.Context.IsLoading == false)
            {
                BoardDomainService.Context.GetBoardMainQuery().IncludeTotalCount = true;
                BoardDomainService.Context.Load(BoardDomainService.Context.GetBoardMainQuery(), LoadBehavior.RefreshCurrent, true);
            }
            else
            {
                MessageData = "조회 중이니 잠시 후 다시 시도해 주시기 바랍니다.";
            }
        }

        //데이터 추가 - SubmitChange()전 까지는 로컬에만 존재한다.
        public void Adding(BOARD_MAIN bm)
        {
            if (bm != null && bm is BOARD_MAIN)
            {
                bm.REG_DT = DateTime.Now;
                bm.REG_IDX = 1;
                BoardDomainService.Context.BOARD_MAINs.Add(bm);
                CurrentData = bm;
            }
            else
            {
                MessageData = "데이터가 없거나 형식이 다릅니다.";
            }
        }

        //데이터 삭제 
        public void Removing(BOARD_MAIN bm)
        {
            if (bm != null && bm is BOARD_MAIN)
            {
                BoardDomainService.Context.BOARD_MAINs.Remove(bm);
            }
            else
            {
                MessageData = "데이터가 없거나 형식이 다릅니다.";
            }
        }

        //저장
        public void Saving()
        {

            //저장된 결과를 알기 위해 콜백 함수를 지정
            BoardDomainService.Context.SubmitChanges(submitCallBack, null);
        }


        //저장 콜백 함수
        private void submitCallBack(SubmitOperation so)
        {
            if (so.IsComplete == true)
            {
                MessageData = "작업을 완료 했습니다.";
            }
            else
            {
                MessageData = "작업을 실패 했습니다.";
            }
        }

        //작업 취소
        public void Canceling()
        {
            BoardDomainService.Context.RejectChanges();
        }

    }
}


4. ViewModel

BoardMainViewModel.cs

using System.ComponentModel;
using System.ComponentModel.Composition;
using System.Windows.Input;
using Microsoft.Expression.Interactivity.Core;
using SL5_BOARD.Models;
using SL5_BOARD.Web.Model;

namespace SL5_BOARD.ViewModels
{
    //뷰모델 익스포트
    [Export(typeof(BoardMainViewModel))]
    public class BoardMainViewModel : INotifyPropertyChanged
    {
        #region PropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnPropertyChanged(string PropertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
        #endregion

         //모델 인포트
        [Import(typeof(BoardMain))]
        public BoardMain BoardMainData { get; set; }

        public BoardMainViewModel()
        {
            //BoardMainData.BoardTypeCol
        }

        //조회커맨드
        ICommand selectCommand;
        public ICommand SelectCommand
        {
            get
            {
                if (selectCommand == null)
                {
                    selectCommand = new ActionCommand(() =>
                    {
                        BoardMainData.Getting();
                    });
                }
                return selectCommand;
            }
        }

        //조회 함수 - 맨처음에 조회를 하기 위해서..
        public void Select()
        {
            BoardMainData.Getting();
        }

        //추가 커맨드
        ICommand addCommand;
        public ICommand AddCommand
        {
            get
            {
                if (addCommand == null)
                {
                    addCommand = new ActionCommand(() =>
                    {
                        BOARD_MAIN bm = new BOARD_MAIN();
                        bm.BOARD_NAME = "신규 게시판의 명칭을 입력하세요";
                        bm.BOARD_DESC = "신규 게시판의 설명을 입력하세요";
                        BoardMainData.Adding(bm);
                    });
                }
                return addCommand;
            }
        }

        //삭제 커맨드
        ICommand removeCommand;
        public ICommand RemoveCommand
        {
            get
            {
                if (removeCommand == null)
                {
                    removeCommand = new ActionCommand(p =>
                    {
                        if (p != null)
                        {
                            BOARD_MAIN bm = p as BOARD_MAIN;
                            BoardMainData.Removing(bm);
                        }
                    });
                }
                return removeCommand;
            }
        }

        //저장 커맨드
        ICommand saveCommand;
        public ICommand SaveCommand
        {
            get
            {
                if (saveCommand == null)
                {
                    saveCommand = new ActionCommand(() =>
                    {
                        BoardMainData.Saving();
                    });
                }
                return saveCommand;
            }
        }

        //취소 커맨드
        ICommand cancelCommand;
        public ICommand CancelCommand
        {
            get
            {
                if (cancelCommand == null)
                {
                    cancelCommand = new ActionCommand(() =>
                    {
                        BoardMainData.Canceling();
                    });
                }
                return cancelCommand;
            }
        }
        
        //리스트 박스에서 선택항목 변경시
        ICommand selectChanged;
        public ICommand SelectChangedCommand
        {
            get
            {
                if (selectChanged == null)
                {
                    selectChanged = new ActionCommand(para => SelectChangedOperation(para));
                }
                return selectChanged;
            }
        }
        private void SelectChangedOperation(object para)
        {
            BoardMainData.CurrentData = para as BOARD_MAIN;
        }

        //라디오 버튼에서 클릭시 - 라디오 버튼;;;이렇게까지 했는대두 결국 잘 앙대구..짬뽕남;;
        ICommand changeStateCommand;
        public ICommand ChangeStateCommand
        {
            get
            {
                if (changeStateCommand == null)
                {
                    changeStateCommand = new ActionCommand(para => ChangeStateOperation(para));
                }
                return changeStateCommand;
            }
        }
        private void ChangeStateOperation(object para)
        {
            BoardMainData.BoardStateText = para as string;
        }
    }
}

5. View

BoardMainView.xaml

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:SL5_BOARD_ViewModels="clr-namespace:SL5_BOARD.ViewModels"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:SL5_BOARD_Converter="clr-namespace:SL5_BOARD.Converter"
    x:Name="userControl"
    x:Class="SL5_BOARD.Views.BoardMainView"
    mc:Ignorable="d"
    d:DesignHeight="500" d:DesignWidth="600">
   
 <UserControl.Resources>

   <!--바인딩 컨버터 사용 할 수 있도록... -->
  <SL5_BOARD_Converter:ValueToBoolConverter x:Key="ValueToBoolConverter"/>
  <SL5_BOARD_Converter:BoardTypeConverter x:Key="BoardTypeConverter"/>
 </UserControl.Resources>
   
    <Grid x:Name="LayoutRoot" Background="White">

     <!--조회 커맨드 실행-->
     <i:Interaction.Triggers>
      <i:EventTrigger>
       <i:InvokeCommandAction Command="{Binding SelectCommand, Mode=OneWay}"/>
      </i:EventTrigger>
     </i:Interaction.Triggers>
     <Grid.ColumnDefinitions>
      <ColumnDefinition Width="192"/>
      <ColumnDefinition/>
     </Grid.ColumnDefinitions>
     <Grid.RowDefinitions>
      <RowDefinition Height="38"/>
      <RowDefinition Height="35"/>
      <RowDefinition/>
      <RowDefinition Height="35"/>
     </Grid.RowDefinitions>
     <Grid.DataContext>

      <!--디자인 타임에서 사용하기 위해 넣어 놓은 부분-->
      <SL5_BOARD_ViewModels:BoardMainViewModel/>
     </Grid.DataContext>

      <!--사실 이걸 다 읽는다고 이쁜 디자인이 되는 것도 아니니..디자인 부분은 넘어가겠다..-->
     <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="게시판 관리" FontSize="14.667" FontWeight="Bold" Foreground="{StaticResource ForeColor_Blues}" Margin="10,6,0,10" d:LayoutOverrides="Height"/>
     <Path Data="M0,30 L400,30" Fill="#FF5181B9" Height="3" Margin="0" Stretch="Fill" Stroke="#FF5181B9" UseLayoutRounding="False" VerticalAlignment="Bottom" StrokeLineJoin="Bevel" StrokeThickness="3" Grid.ColumnSpan="2"/>
     <Button x:Name="btnSelect" Content="조회" HorizontalAlignment="Right" Margin="0,6,8,7" Width="70" Grid.Column="1" Grid.Row="1" Height="22"/>
     <Path Data="M0,30 L400,30" Fill="{StaticResource ForeColor_Kakis}" Stretch="Fill" Stroke="{StaticResource ForeColor_Kakis}" UseLayoutRounding="False" StrokeLineJoin="Bevel" StrokeThickness="3" Grid.ColumnSpan="2" Grid.Row="1" Height="3" VerticalAlignment="Bottom" Margin="0,0,0,-1"/>
     <TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="게시판 목록" FontSize="12" FontWeight="Bold" Foreground="{StaticResource ForeColor_Kakis}" Margin="10,0,0,368" Grid.Row="2" VerticalAlignment="Bottom"/>

 

     <!--아까 모델에서 만들었던 CollectionData를 여기 ItemsSource에 바인딩 한다. 그리고, 아이템 템플릿을 이용해서 데이터를 뿌려준다.-->
     <ListBox x:Name="lbBoardMain" Margin="8,27,8,8" Grid.Row="2" BorderBrush="{x:Null}" ItemsSource="{Binding BoardMainData.CollectionData}" ItemTemplate="{StaticResource BoardMainDataTemplate}">
      <i:Interaction.Triggers>

       <!--아까 뷰모델에서 만들어 놓았던 SelectChangedCommand를 여기서 사용한다-->
       <i:EventTrigger EventName="SelectionChanged">
        <i:InvokeCommandAction Command="{Binding SelectChangedCommand, Mode=OneWay}" CommandParameter="{Binding SelectedItem, ElementName=lbBoardMain}"/>
       </i:EventTrigger>
      </i:Interaction.Triggers>
     </ListBox>

     <TextBlock TextWrapping="Wrap" Text="게시판 상세 정보" FontSize="12" FontWeight="Bold" Foreground="{StaticResource ForeColor_Kakis}" Margin="8,0,0,367" Grid.Row="2" VerticalAlignment="Bottom" HorizontalAlignment="Left" Grid.Column="1"/>

     <!--게시판 상세 정보가 들어가는 스택패널 여기서 CurrentData를 바인딩 한다.-->
     <StackPanel x:Name="spBoardMainInfo" Margin="8,27,8,8" Grid.Column="1" Grid.Row="2" DataContext="{Binding BoardMainData.CurrentData}">
      <StackPanel Orientation="Horizontal" Height="28">
       <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
       <TextBlock TextWrapping="Wrap" Text="명칭" FontSize="12" VerticalAlignment="Center" Width="70" Padding="5,0,0,0"/>
       <TextBox TextWrapping="Wrap" Text="{Binding BOARD_NAME, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}"/>
      </StackPanel>

      <StackPanel Orientation="Horizontal" Height="28">
       <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
       <TextBlock TextWrapping="Wrap" Text="종류" FontSize="12" VerticalAlignment="Center" Width="70" Padding="5,0,0,0"/>
       <ComboBox Width="120" VerticalAlignment="Center" Foreground="{StaticResource NormalForeColor}" ItemsSource="{Binding Path=DataContext.BoardMainData.BoardTypeCol, ElementName=LayoutRoot}" DisplayMemberPath="BoardTypeComboText" SelectedValuePath="BoardType" SelectedValue="{Binding BOARD_TYPE, Mode=TwoWay}"/>
      </StackPanel>

      <StackPanel Orientation="Horizontal" Height="28">
       <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
       <TextBlock TextWrapping="Wrap" Text="로그인확인" FontSize="12" VerticalAlignment="Center" Width="70" Padding="5,0,0,0"/>
       <CheckBox Content="{Binding MEMBER_CHECK}" VerticalAlignment="Center" Foreground="{StaticResource NormalForeColor}" IsChecked="{Binding MEMBER_CHECK, Mode=TwoWay}"/>
      </StackPanel>

      <StackPanel Orientation="Horizontal" Height="28">
       <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
       <TextBlock TextWrapping="Wrap" Text="설명" FontSize="12" VerticalAlignment="Center" Width="70" Padding="5,0,0,0"/>
       <TextBox TextWrapping="Wrap" Text="{Binding BOARD_DESC, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}"/>
      </StackPanel>

      <StackPanel Orientation="Horizontal" Height="28" >
       <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
       <TextBlock TextWrapping="Wrap" Text="상태" FontSize="12" VerticalAlignment="Center" Width="70" Padding="5,0,0,0"/>
       <RadioButton x:Name="radioButton" Content="준비중" VerticalAlignment="Bottom" Foreground="{StaticResource NormalForeColor}" Margin="0,0,0,5" GroupName="BoardState"
     IsChecked="{Binding ElementName=LayoutRoot, Path=DataContext.BoardMainData.BoardStateText, ConverterParameter=준비중, Converter={StaticResource ValueToBoolConverter}, Mode=OneWay}" Command="{Binding DataContext.ChangeStateCommand, ElementName=LayoutRoot}" CommandParameter="{Binding Content, ElementName=radioButton}"/>
       <RadioButton x:Name="radioButton1" Content="사용중" VerticalAlignment="Bottom" Foreground="{StaticResource NormalForeColor}" Margin="8,0,0,5" GroupName="BoardState"
     IsChecked="{Binding ElementName=LayoutRoot, Path=DataContext.BoardMainData.BoardStateText, ConverterParameter=사용중, Converter={StaticResource ValueToBoolConverter}, Mode=OneWay}" Command="{Binding DataContext.ChangeStateCommand, ElementName=LayoutRoot}" CommandParameter="{Binding Content, ElementName=radioButton1}"/>
       <RadioButton x:Name="radioButton2" Content="일시중지" VerticalAlignment="Bottom" Foreground="{StaticResource NormalForeColor}" Margin="8,0,0,5" GroupName="BoardState"
     IsChecked="{Binding ElementName=LayoutRoot, Path=DataContext.BoardMainData.BoardStateText, ConverterParameter=일시중지, Converter={StaticResource ValueToBoolConverter}, Mode=OneWay}" Command="{Binding DataContext.ChangeStateCommand, ElementName=LayoutRoot}" CommandParameter="{Binding Content, ElementName=radioButton2}"/>
       <RadioButton x:Name="radioButton3" Content="삭제" VerticalAlignment="Bottom" Foreground="{StaticResource NormalForeColor}" Margin="8,0,0,5" GroupName="BoardState"
     IsChecked="{Binding ElementName=LayoutRoot, Path=DataContext.BoardMainData.BoardStateText, ConverterParameter=삭제, Converter={StaticResource ValueToBoolConverter}, Mode=OneWay}" Command="{Binding DataContext.ChangeStateCommand, ElementName=LayoutRoot}" CommandParameter="{Binding Content, ElementName=radioButton3}"/>
      </StackPanel>

            <StackPanel Orientation="Horizontal" Height="28">
             <Ellipse Fill="#FF666666" Stroke="Black" Width="5" Height="5" VerticalAlignment="Center" Margin="10,0,0,0"/>
             <TextBlock TextWrapping="Wrap" Text="순서" FontSize="12" VerticalAlignment="Center" Width="70" Padding="5,0,0,0"/>
             <TextBox TextWrapping="Wrap" Text="{Binding BOARD_ORDER, Mode=TwoWay}" Foreground="{StaticResource NormalForeColor}" BorderThickness="0,0,0,1" FontSize="12" VerticalAlignment="Center" Style="{StaticResource NormalTextBoxStyle}"/>
            </StackPanel>

     </StackPanel>
     <!--게시판 상세 정보 패널 끝-->

      <!--각종 버튼들-->
    <Button x:Name="btnAdd" Content="추가" Margin="0,3,8,0" Grid.Row="2" VerticalAlignment="Top" HorizontalAlignment="Right" Width="70" Height="22">
      <i:Interaction.Triggers>
       <i:EventTrigger EventName="Click">
        <i:InvokeCommandAction Command="{Binding AddCommand, Mode=OneWay}"/>
       </i:EventTrigger>
      </i:Interaction.Triggers>
     </Button>

     <Button x:Name="btnSave" Content="저장" Margin="0,3,82,0" Grid.Row="2" VerticalAlignment="Top" Height="22" Grid.Column="1" HorizontalAlignment="Right" Width="70">
      <i:Interaction.Triggers>
       <i:EventTrigger EventName="Click">
        <i:InvokeCommandAction Command="{Binding SaveCommand, Mode=OneWay}"/>
       </i:EventTrigger>
      </i:Interaction.Triggers>
     </Button>

     <Button x:Name="btnCancel" Content="취소" Margin="0,3,8,0" Grid.Row="2" VerticalAlignment="Top" Height="22" Grid.Column="1" HorizontalAlignment="Right" Width="70">
      <i:Interaction.Triggers>
       <i:EventTrigger EventName="Click">
        <i:InvokeCommandAction Command="{Binding CancelCommand, Mode=OneWay}"/>
       </i:EventTrigger>
      </i:Interaction.Triggers>
     </Button>
     <TextBlock Margin="8,6,82,8" Grid.Row="1" TextWrapping="Wrap" Text="{Binding BoardMainData.MessageData, Mode=OneWay}" Grid.ColumnSpan="2" VerticalAlignment="Center" FontSize="12"/>

    </Grid>
</UserControl>

BoardMainView.xaml.cs

using System.ComponentModel.Composition;
using System.Windows;
using System.Windows.Controls;
using SL5_BOARD.ViewModels;

namespace SL5_BOARD.Views
{
    //아직 이넘은 익스포트 앙시켰음
    public partial class BoardMainView : UserControl
    {
        //뷰모델 임포트
        [Import(typeof(BoardMainViewModel))]
        public BoardMainViewModel BoardMainVM { get; set; }

        public BoardMainView()
        {
            InitializeComponent();

            //MEF 초기화
            CompositionInitializer.SatisfyImports(this);

            //ViewModel을 최상위 DataContext에 쑤셔넣구
            LayoutRoot.DataContext = BoardMainVM;
        }

    }
}


6. 너무 길다..작업 시간이 어제 저녁 부터 시작해서 오늘 오후에 끝났으니..음..한 7-8 시간은 작업을 한듯하니..양도 많아서..한번에 올리기가 부담스러운.. 작업을 하면서도 이거 게시판에 어떻게 올리나 한참 궁시렁 거리면서 작업을 했었는데..흐흐흐..원래는 개발하면서 주석을 잘 다는 편인데..이상하게 실버라이트 만들때는 주석 달기가 싫타;; 그래서 소스에 주석이 없어서 소스를 올려도 보기가 쉽지는 않치만..음음.. 일단 뭔가 제대로 돌아가는 듯한 모양을 갖추었으니.. 전체 소스를 한번 올리도록 하겠다.

 여기서 다루지 않았던 컨버터 부분과 게시판 종류를 콤보박스에 표시하기 위해서 약간 먼가 처리한 부분들을 추가로 보면 되고.. 만은 도움이 되었으면 한다. 그리고 디자인 해주실분~ 다시 구해본다..(구해질때까징..ㅜㅜ) 이제 좀있음 여러 화면 떠서 아이폰에서 클릭하면 옆으로 휙하고 지나가고 다시 돌아오는 애니메이션 기능 넣구 싶은뎅..이힝..알려주실분도 좋구..도와주실분도..좋쿠..좀..

Posted by MVP kaki104

일단 디자인 레이아웃은..실버라이트 코리아의 디자인을 빼꼈다는..쿨럭 ^^;;;;;디자인 해줄 분이 없는 관계로..ㅜㅜ 그냥 기본 디자인만 잡아 봤고, 이제 슬슬 본격 적으로 사용해야할 폴더들을 만들어 놓고, 리소스딕셔너리도 추가해 놓구 ㅎㅎ

1. 기본 메인 디자인

디자인 정말 거시기한.. 다시 한번 디자인 해주실 구해야 겠다는..손좀 들어보시죠??(ㅡㅡ+) 이것이 통짜 실버라이트 게시판...이 될 예정인 화면! 냐하하..음음..이 소스는..덩치가 큰 관계로 일부 파일만 첨부를 해서 올리도록 하겠다..(머 소스 업어두 아무도 신경쓰지 앙치만..ㅜㅜ 나랑 같이 만들고 있는 사람 손좀;;)

2. 중요 부분

2-1. 폴더 추가
SL5_BOARD 프로젝
-> Images, Models, Resources, ViewModels, Views

2-2. 일단 기존에 있던 내용들은 싹다 지우고 새로 그린 것이고, 이렇게 디자인을 할때 블랜드를 사용하지 앙고 디자인 하면 무지 스트레스 받으니, 미리미리 블랜드 사용법도 익혀 두는 것이 정신 건강에 도움이 된다는..

2-3. 이미지는 뷰박스로 싸메서 사용한다. 그래야 이미지 크기가 변경대구 하더라도 보기 좋다.

<Viewbox Stretch="Fill" Grid.Row="1" Margin="6,0">
 <Image x:Name="ImgLogo" Source="Images/logoPixelImage.jpg" Stretch="None"/>
</Viewbox>

참, 여기서...프로젝트에 이미지를 포함하고 불러다가 사용할 때.. 이미지 파일의 속성에 들어가서 Build Action을 꼭 Resource로 지정하고 불러다가 사용해야한다. 음음..이거 몰라서 몇 시간 허비하면 눈물난다..

2-4. 동일한 스타일이나, 컬러등은 리소스로 만들어서 사용한다. 이 리소스 만드는 작업이 블랜드를 사용하면 메뉴 하나 클릭하면 바로 만들어 지지만 VS2010에서는 일일이 수작업으로 만들어야 한다.

* 수동 작업 방법
Resources폴더에서 Add -> New Item -> Resources File 추가

BoardResourceDictionary.xaml

 <ResourceDictionary
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
 <SolidColorBrush x:Key="CountColor" Color="#FFFF1E1E"/>
 <!-- Resource dictionary entries should be defined here. -->
</ResourceDictionary>


 App.xaml

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             x:Class="SL5_BOARD.App"
             >
    <Application.Resources>
       
     <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
       <ResourceDictionary Source="Resources/BoardResourceDictionary.xaml"/>
      </ResourceDictionary.MergedDictionaries>
     </ResourceDictionary>
       
    </Application.Resources>
</Application>


이렇게 만들어 놓으면 어떤 화면이서든지 리소스를 xaml에 바로 적용할 수 있다.

<StackPanel Margin="4,0" Orientation="Horizontal">
 <TextBlock TextWrapping="Wrap" Text="게시물" FontSize="12" Width="40"/>
 <TextBlock TextWrapping="Wrap" Text="44" FontSize="12" FontWeight="Bold" Margin="5,0,0,0" Foreground="{StaticResource CountColor}"/>
</StackPanel>

2-5. 디자인 하면서 까다로운 작업 중 하나가.. 스크롤 관련 문제이다..이 화면도 메뉴 부분을 스크롤 시키기 위해서 약간 시간이 걸렸는데.. 머리를 열심히 굴려야 왜 저렇게 해야하는지 이해가 된다..이걸 VS2010에서만 작업해서 만들려고 하면 ...머리에서 쥐가..쿨럭..

<Border x:Name="MainMenu" BorderBrush="#FFAED8DC" BorderThickness="4" Grid.Row="1" Margin="0,8,0,0" CornerRadius="10" Padding="6">
 <ScrollViewer BorderThickness="0">
  <StackPanel Margin="0">
   <ListBox BorderThickness="0">
    <ListBoxItem Content="전체글보기" BorderThickness="0"/>
    <ListBoxItem Content="카페태그보기"/>
    <ListBoxItem Content="베스트게시물"/>
    <ListBoxItem Content="소셜앱"/>
    <ListBoxItem Content="공지/수요소식"/>
    <ListBoxItem Content="매일매일 출석~"/>
    <ListBoxItem Content="자기소개~"/>
    <ListBoxItem Content="자유로운글"/>
    <ListBoxItem Content="멋쟁이 사진첩"/>
    <ListBoxItem Content="자유사진첩"/>
    <ListBoxItem Content="세미나/행사/자료"/>
    <ListBoxItem Content="회원작품"/>
    <ListBoxItem Content="RIA 구현 사례"/>
    <ListBoxItem Content="구인/구직"/>
    <ListBoxItem Content="운영자카페"/>
    <ListBoxItem Content="운영자게시판"/>
    <ListBoxItem Content="실버라이트"/>
    <ListBoxItem Content="강좌"/>
    <ListBoxItem Content="Tips And Tricks"/>
   </ListBox>
  </StackPanel>
 </ScrollViewer>
</Border>

2-6. 메인 컨텐츠가 들어갈 부분은 ItemsControl로 작업 했다. 그곳에 다른 곳에서 만들어 놓은 UserControl을 계속 번갈아 가며 올릴 것이다. 음..이것 말고 Page를 쓸까 생각해 봤는데..Page를 사용했을 때 네비게이션 기능 말고는..별로 없는 것 같아서..이번에는 이걸로 하기로 했다.


3. 디자인이 되니 뭔가 보이는 것 같다. 빨리 진행해서 이번주 안에 1차 완성을 할 수 있었으면 한다. 그리고...디자인 해줄 분 다시 한번 구한당..ㅜㅜ

 ****첨부 파일은 SL5_BOARD 프로젝트만 포함되었다..전체 소스가 아니니 실행은 앙된다..^^;;; 전체 소스가 필요한 사람은 리플로 요청을 하면 개별 적으로 보내 주는 것으로....하지만 본인이 직접 만들어 봐야 실력이 늘어나니..열심히 화이팅~

Posted by MVP kaki104

티스토리 툴바