간단한 콤보박스에 바인딩하는 예제를 만들어 보았다. 일단 화면을 보자

WCF RIA 서비스를 이용해서 3개의 데이터를 조회한 후에 상품은 데이터 그리드에 바인딩하고, 오른쪽 각각의 콤보박스에 카테고리, 공급자를 바인딩을 해 놓았다. 그리고 그리드에서 선택된 데이터를 변경하면 콤보박스에 데이터가 변경되고, 콤보박스에서 데이터를 변경하면 그리드의 데이터가 변경되도록 만들어 놓았다.

바인딩의 기초적인 내용인데 처음에 만들려고 하면 좀 까다로운 부분이 있다. 예제를 참고해서 연습하면 바인딩 마스터를 할 수 있을 것이다.

ComboBoxSampleView.xaml

<UserControl x:Class="SL4_RIA_Sample.ComboBoxSampleView"
    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"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400"
    xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.DomainServices"
    xmlns:my="clr-namespace:SL4_RIA_Sample.Web"
    xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
   
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="0.5*" />
            <ColumnDefinition Width="0.5*" />
        </Grid.ColumnDefinitions>


        <!--상품 조회용 리아 컨트롤-->

        <riaControls:DomainDataSource AutoLoad="True" Height="0" LoadedData="productDomainDataSource_LoadedData" x:Name="productDomainDataSource" QueryName="GetProductsQuery" Width="0">
            <riaControls:DomainDataSource.DomainContext>
                <my:DomainService1 />
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>


        <!--카테고리 조회용 리아컨트롤-->

        <riaControls:DomainDataSource AutoLoad="True" Height="0" LoadedData="categoryDomainDataSource_LoadedData" x:Name="categoryDomainDataSource" QueryName="GetCategoriesQuery" Width="0">
            <riaControls:DomainDataSource.DomainContext>
                <my:DomainService1 />
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>


         <!--공급자 조회용 리아컨트롤-->

        <riaControls:DomainDataSource AutoLoad="True" Height="0" LoadedData="supplierDomainDataSource_LoadedData" x:Name="supplierDomainDataSource" QueryName="GetSuppliersQuery" Width="0">
            <riaControls:DomainDataSource.DomainContext>
                <my:DomainService1 />
            </riaControls:DomainDataSource.DomainContext>
        </riaControls:DomainDataSource>


        <!--그리드-->

        <sdk:DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Data, ElementName=productDomainDataSource}" x:Name="productDataGrid" RowDetailsVisibilityMode="VisibleWhenSelected">
            <sdk:DataGrid.Columns>
                <sdk:DataGridTextColumn x:Name="product_NameColumn" Binding="{Binding Product_Name}" Header="Product Name" Width="SizeToHeader" />
             <sdk:DataGridTextColumn x:Name="english_NameColumn" Binding="{Binding English_Name, Mode=TwoWay}" Header="English Name" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="category_IDColumn" Binding="{Binding Category_ID, Mode=TwoWay}" Header="Category ID" Width="SizeToHeader" />
             <sdk:DataGridTextColumn x:Name="product_IDColumn" Binding="{Binding Product_ID, Mode=OneWay}" Header="Product ID" IsReadOnly="True" Width="SizeToHeader" />
                <sdk:DataGridTextColumn x:Name="supplier_IDColumn" Binding="{Binding Supplier_ID, Mode=TwoWay}" Header="Supplier ID" Width="SizeToHeader" />
            </sdk:DataGrid.Columns>
        </sdk:DataGrid>

 

        <StackPanel Grid.Column="1" Margin="4,0,0,0" >
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Product Name" Width="90"/>
                <TextBox Text="{Binding SelectedItem.Product_Name, ElementName=productDataGrid, Mode=TwoWay}" />
            </StackPanel>


            <!--카테고리 콤보박스-->

            <StackPanel Orientation="Horizontal">
                <TextBlock Text="Category" Width="90" />
                <ComboBox ItemsSource="{Binding Data, ElementName=categoryDomainDataSource, Mode=OneWay}" DisplayMemberPath="Category_Name" SelectedValuePath="Category_ID" SelectedValue="{Binding SelectedItem.Category_ID, ElementName=productDataGrid, Mode=TwoWay}" />
            </StackPanel>


            <!--공급자 콤보박스-->
            <StackPanel Orientation="Horizontal">
             <TextBlock Text="Supplier" Width="90" />
             <ComboBox ItemsSource="{Binding Data, ElementName=supplierDomainDataSource, Mode=OneWay}" DisplayMemberPath="Company_Name" SelectedValuePath="Supplier_ID" SelectedValue="{Binding SelectedItem.Supplier_ID, ElementName=productDataGrid, Mode=TwoWay}" />
            </StackPanel>
        </StackPanel>
    </Grid>
</UserControl>


소스를 실행하여 분석해보면...
 어떻게 바인딩을 했는지 확인 할 수 있다. 궁금한 점이나 요청사항은 항상 리플로 남겨주면 매우 친절한 설명을 추가하도록 하겠다..^^

블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

지난번 강좌에 사진 촬영에 대한 내용을 다루었다..그러나 사진 촬영만 하면 무었을 하겠는가..어디다가 저장을 해야하는데..물론 실버라이트는 기본적으로 격리저장소(Isolated Storage)를 제공하고 있어서, 처음에는 사진을 촬영해서 그곳에 저장하고 그걸 다시 서버로 전송을 할려는 계획을 세우고 이것 저것 찾아보았는데..

서버로 파일을 업로드할려고 찾았던 컨트롤이 절대 경로를 가지고 파일 정보를 가지고 올 수 있어야지만, 업로드가 가능했던 것이다.하지만, 격리저장소는 OOB 상태가 아니라면 실버라이트4 응용프로그램이 절대 경로를 사용해서는 접근이 불가능하다..(이런 내용을 알기위해 삽질했던 시간이 2틀 정도였던 것 같다..ㅡㅡ;;;)

그렇다면, 사진을 찍어서 로컬에 저장하지 않고, 그냥 서버로 바로 던져서 서버에 저장을 해야겠다는 결론을 내리고 사이트를 검색하다가 찾은 것이 WCF RIA Service를 사용한 파일 업로드 방식이였다. 처음에 그걸 발견하고, 완전 기뻐서 땡큐를 연발하면서 리플을 남겼었다.

강좌를 위해서 소스를 좀 수정하고, 간단하게 목록까지 볼 수 있도록 수정을 했다.

이 화면의 구성은 왼쪽 리스트 박스에서 클릭을 하면 오른쪽에 큰 이미지를 보여 줄려는 의도로 만들었다. 그러나..시간 관계상..해당 기능을 구현하지는 않았다. 여러분들께서 만들어서 올려 줄것이라고 믿는다. ^^ 또..사진 촬영을 2번하면 화면이 먹통이 되는 이상 현상이 있는데..이 부분도 수정해서 완성 올려주면 좋겠다. 그래서 구현되어 있는 기능은

1) 웹켐 촬영

2) 촬영된 이미지를 Jpeg로 변환

3) 변환된 이미지를 WCF RIA Service를 이용해서 서버로 전송

4) 서버에 이미지 저장

5) 저장된 이미지 목록 WCF RIA Service로 조회

6) 조회된 파일 목록을 가지고 리스트박스에 뿌려주면서 실제 이미지를 웹서버에서 불러와서 보여준다.


1. 서버작업

SL4_RIA_Sample.Web 프로젝트에

Assets 폴더를 하나 추가
FileHandler.cs, WebFile.cs를 추가한다.

WebFile.cs

using System.ComponentModel.DataAnnotations;

namespace SL4_RIA_Sample.Web.Assets
{
    //전송될 파일 클래스
    public class WebFile
    {  //WCF RIA Service의 DTO로 사용하기 위해서는 반드시 Key가 필요하다.
        [Key]
        public string FileName { get; set; }
        public byte[] FileContent { get; set; }
    }
}

무지 심플한 클래스다. 이 클래스가 파일을 마치 레코드 다루듯이 가지고 올것이다.

FileHandler.cs

using System.Web;
using System.Web.Configuration;
using System.IO;

namespace SL4_RIA_Sample.Web.Assets
{
    /// <summary>
    /// http://community.infragistics.com/blogs/anton_staykov/archive/2010/04/28/implementing-simple-file-upload-using-silverlight-4-drag-amp-drop-feature-and-infragistics-compression-library.aspx
    /// 이곳에 있던 소스를 참고로 수정
    /// 파일핸들러 스택틱 클래스 웹파일을 로컬(서버)에 저장하는 역할을 한다.
    /// </summary>
    public static class FileHandler
    {
        /// <summary>
        /// 파일전송 함수
        /// </summary>
        /// <param name="file">웹파일 클래스</param>
        public static void HandleFile(WebFile file)
        {
            //업로드 폴더 지정(Web.Config 파일에 절대 경로 존재)
            string uploadDir = WebConfigurationManager.AppSettings["UploadDir"];
            //업로드 폴더가 존재하면
            if (!string.IsNullOrEmpty(uploadDir))
            {
                //업로드 폴더명에 ~/ 이런 글씨가 있으면
                if (uploadDir.IndexOf("~/") == 0)
                    //상대경로를 절대경로로 변경
                    uploadDir = HttpContext.Current.Server.MapPath(uploadDir);
                //업로드 폴더명에 /이것만 있으면
                if (uploadDir.LastIndexOf("/") == uploadDir.Length - 1)
                    //절대경로이니 대충 짤라서 사용
                    uploadDir = uploadDir.Substring(0, uploadDir.Length - 1);

                //저장할 풀경로를 만들고
                string fullFileName = string.Format("{0}/{1}", uploadDir, file.FileName);

                //파일 존재여부 확인
                if (File.Exists(fullFileName))
                {
                    //확장자
                    string ext = fullFileName.Substring(fullFileName.LastIndexOf("."));
                    //파일명
                    string fName = fullFileName.Substring(0, fullFileName.LastIndexOf("."));
                    //풀파일명
                    fullFileName = string.Format("{0}_1{1}", fName, ext);
                }
                //저장~
                File.WriteAllBytes(fullFileName, file.FileContent);
            }
        }
    }
}

위의 소스를 보니 Web.config에 업로드 폴더도 지정해야한다.

Web.config 파일에 추가
  <!--업로드폴더 지정-->
  <appSettings>
    <add key="UploadDir" value="~/UserUploads" />
  </appSettings>

음..그럼 이걸 사용하는 WCF RIA Service를 만들자

FileUploaderDomainService.cs

namespace SL4_RIA_Sample.Web
{
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.ServiceModel.DomainServices.Hosting;
    using System.ServiceModel.DomainServices.Server;
    using System.Web;
    using System.Web.Configuration;
    using IDApp.Web.Assets;

    /// <summary>
    /// 파일 업로드용 WCF RIA Service 생성
    /// </summary>
    [EnableClientAccess()]
    public class FileUploaderDomainService : DomainService
    {
        //파일 목록들 조회
        public IQueryable<WebFile> GetFiles()
        {
            //업로드 폴더명 가지고 가서
            string path = WebConfigurationManager.AppSettings["UploadDir"];

            //웹파일 정보 리스트 객체를 만들고,
            List<WebFile> webFiles = new List<WebFile>();

            //폴더에 내용이 없으면 그냥 빈거 던지고
            if (string.IsNullOrEmpty(path))
                return webFiles.AsQueryable();

            //디렉토리 정보를 가지고 오고
            DirectoryInfo di = new DirectoryInfo(HttpContext.Current.Server.MapPath(path));
            //파일정보도 가지고 오고
            foreach (FileInfo file in di.GetFiles())
            {
                //웹파일 정보 리스트에 하나씩 추가하고
                webFiles.Add(new WebFile { FileName = file.Name });
            }
            //결과를 던진다.
            return webFiles.AsQueryable();
        }

        //파일 업로드 함수
        public void InsertFile(WebFile file)
        {
            FileHandler.HandleFile(file);
        }
    }
}

마지막으로 프로젝트에 UserUploads 폴더를 추가해 놓는다.
여기 까지 작업을 하면 서버쪽 작업은 일단 완료된다. 컴파일을 해서 오류가 있는지 확인해 본다. 오류가 없으면 클라이언트쪽 작업을 시작한다.


2. 클라이언트 작업

일단 사진을 촬영하면 BitmapImage으로 만들어 진다. 우린 이걸 Jpeg로 변환해야하는데 이미지 컨버트용 오픈소스 라이브러리를 사용한다.

FluxJpeg 라는 라이브러리(https://github.com/briandonahue/FluxJpeg.Core)
위의 주소를 가면 전체 소스를 Download를 받을 수 있고, 다운로드를 받아서 소스를 VS2010으로 열고, 빌드를 해주면 FJ.Core.dll이란 파일이 만들어 진다. 그 DLL파일을 프로젝트에 추가해서 사용한다. 일단 이 소스에도 해당 Dll파일이 포함되어져 있다. Bin\Debug 폴더를 보면 FJ.Core.dll파일을 찾을 수 있다. 파일을 폴더에 복사해 놓은 후 Add Reference로 해당 dll을 프로젝트 리퍼런스로 추가해 놓는다. 지금 추가한 라이브러리를 편하게 사용하기 위해서 확장메소드 기능을 사용한다.


Extension.cs
이 확장메소드의 소스는 복사해 온것이기 때문에..주석이 없다..

using System.IO;
using System.Windows.Media.Imaging;
using FluxJpeg.Core;
using FluxJpeg.Core.Encoder;

namespace SL4_RIA_Sample
{
    //확장메소드 만들기
    public static class Extension
    {
        public static void EncodeJpeg(this WriteableBitmap bitmap, MemoryStream stream, int quality = 90)
        {
            int width = bitmap.PixelWidth;
            int height = bitmap.PixelHeight;
            int bands = 3;
            byte[][,] raster = new byte[bands][,];
            for (int i = 0; i < bands; i++)
            {
                raster[i] = new byte[width, height];
            }
            for (int row = 0; row < height; row++)
            {
                for (int column = 0; column < width; column++)
                {
                    int pixel = bitmap.Pixels[width * row + column];
                    raster[0][column, row] = (byte)(pixel >> 16); //R
                    raster[1][column, row] = (byte)(pixel >> 8);  //G
                    raster[2][column, row] = (byte)pixel;         //B
                }
            }
            ColorModel model = new ColorModel { colorspace = ColorSpace.RGB };
            FluxJpeg.Core.Image img = new FluxJpeg.Core.Image(model, raster);

            JpegEncoder encoder = new JpegEncoder(img, quality, stream);
            encoder.Encode();

            stream.Seek(0, SeekOrigin.Begin);
        }
    }
}

확장메소드가 무엇인지..어떻게 사용하는지는 잠시 후에 보도록 하겠다.

확장메소드까지 작업을 했다면..사진을 보여주는 화면을 보도록 하자..

PhotoView.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:SL4_RIA_Sample_Web_DomainService="clr-namespace:SL4_RIA_Sample.Web"
    xmlns:local="clr-namespace:SL4_RIA_Sample" x:Class="SL4_RIA_Sample.PhotoView"
    mc:Ignorable="d" Loaded="UserControl_Loaded"
    d:DesignHeight="300" d:DesignWidth="400">

    <UserControl.Resources>
        <local:ImageFullPathConverter x:Key="ImageFullPathConverter"/>

        <!--리스트박스에서 사용하는 데이터 템플릿-->
        <DataTemplate x:Key="ListDataTemplate">
            <Grid d:DesignWidth="97" d:DesignHeight="99">
                <Grid.RowDefinitions>
                    <RowDefinition Height="0.192*"/>
                    <RowDefinition Height="0.808*"/>
                </Grid.RowDefinitions>
                <TextBlock TextWrapping="Wrap" Text="{Binding FileName}"/>
                <Viewbox Grid.Row="1" HorizontalAlignment="Left">
                    <Image Source="{Binding FileName, Converter={StaticResource ImageFullPathConverter}}"
                           Width="120" Margin="5"/>
                </Viewbox>
            </Grid>
        </DataTemplate>
    </UserControl.Resources>
   
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="177" />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="38" />
            <RowDefinition Height="261*" />
        </Grid.RowDefinitions>
       
        <Grid.DataContext>

            <!--블랜드에서 바인딩 작업 할려고 추가해 놓았던 것-->
            <SL4_RIA_Sample_Web_DomainService:FileUploaderDomainContext/>
        </Grid.DataContext>

        <!--상단 버튼-->       
        <StackPanel Orientation="Horizontal" Grid.ColumnSpan="2">
            <Button Content="웹켐 촬영" Width="80" Margin="4" Click="Button_Click" />
            <Button Content="이미지 목록" Width="80" Margin="4" Click="Button_Click_1" />
        </StackPanel>

        <!--리스트박스-->
        <ListBox Grid.Row="1"
                 ItemTemplate="{StaticResource ListDataTemplate}"
                 ItemsSource="{Binding WebFiles, Mode=OneWay}"
                 HorizontalContentAlignment="Stretch"
                 VerticalContentAlignment="Stretch"/>
       
    </Grid>
</UserControl>


PhotoView.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using SL4_RIA_Sample.Web;

 

namespace SL4_RIA_Sample
{
    public partial class PhotoView : UserControl
    {
        //WCF RIA Service 컨텍스트 생성
        FileUploaderDomainContext ctx;

 

        public PhotoView()
        {
            InitializeComponent();
        }

 

        /// <summary>
        /// 촬영시작 버튼
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            WebCam_Child webCam = new WebCam_Child();
            webCam.Show();
        }

        /// <summary>
        /// 유저컨트롤 로드 완료 이벤트
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {

            //초기화
            ctx = new FileUploaderDomainContext();

            //DataContext에 도메인서비스 자체를 넣어놓음 (물론 좋은 방법은 아니니..옵저블 컬렉션을 사용 권고)

            LayoutRoot.DataContext = ctx;
        }

 

        /// <summary>
        /// 이미지 목록 버튼
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Button_Click_1(object sender, RoutedEventArgs e)
        {
            ctx.Load(ctx.GetFilesQuery(), true);
        }
    }
}

여기까지 작업을 했으면 중요한 한가지가 있다. 리스트박스에 이미지 이름을 바인딩하는데 이미지 이름 바인딩 하면서 바로 ImageFullPathConverter가 필요한데..여기서 하는 일이 중요하다.


ImageFullPathConverter.cs

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Data;

 

namespace SL4_RIA_Sample
{
    public class ImageFullPathConverter : IValueConverter
    {

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            string fileName = value as string;
            string ReturnValue;

            //호스트 Uri구함
            string hostUri = App.Current.Host.Source.ToString().Replace(App.Current.Host.Source.LocalPath, "");
            ReturnValue = hostUri + "/UserUploads/" + fileName;

            return ReturnValue;
        }


        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

이미지들은 서버에 존재하기 때문에 서버의 주소를 이미지 이름 앞에 추가해 주는 기능을 한다.

그럼 이제 마지막으로


WebCam_Child.xaml.cs 파일을 수정해 보자


using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.IO;
using SL4_RIA_Sample.Web;
using SL4_RIA_Sample.Web.Assets;
using System.ServiceModel.DomainServices.Client;

namespace SL4_RIA_Sample
{
    public partial class WebCam_Child : ChildWindow
    {
        private CaptureSource _capture;
        //일단 여기다가 저장해놨다가, 성공하면 프로퍼티로 이동
        private string captureFileName;
        //다른 화면에서 저장된 파일명 참고
        public string CaptureFileName;


        public WebCam_Child()
        {
            InitializeComponent();
        }

        private void ChildWindow_Loaded(object sender, RoutedEventArgs e)
        {
            //캡춰소스 인스턴스
            _capture = new CaptureSource();
            _capture.CaptureImageCompleted += new EventHandler<CaptureImageCompletedEventArgs>(_capture_CaptureImageCompleted);
        }

        /// <summary>
        /// 이미지 캡처 완료되었을 때 발생하는 이벤트
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _capture_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
        {
            //이미지 캡춰가 완료되면 여기로
            WriteableBitmap wb = e.Result;
            //화면에 촬영된 이미지 보여주기
            if (_capture != null)
            {
                _capture.Stop();
                ImageBrush image = new ImageBrush();
                image.ImageSource = wb;
                rectVideo.Fill = image;
            }

            //파일명 생성
            string name = Guid.NewGuid().ToString() + ".bmp";
            //메모리 스트림 인스턴스
            MemoryStream stream = new MemoryStream();

            //확장메소드를 이용해서 이미지를 jpeg 포멧으로 변환
            wb.EncodeJpeg(stream);

            //WCF RIA Service 컨텍스트 생성
            FileUploaderDomainContext ctx = new FileUploaderDomainContext();
            //웹파일 정보 하나 만들고
            WebFile file = new WebFile { FileName = name };
            //파일 컨텐츠에 메모리 스트림의 버퍼데이터를 쓸어 넣는다.
            //메모리 스트림아니라 파일 스트림의 데이터도 넣을 수 있다.
            file.FileContent = stream.GetBuffer();
            //웹파일스에..방금 만든거 추가..마치 레코드 추가하듯이
            ctx.WebFiles.Add(file);
            //그리고 세이브체인지 호출~
            ctx.SubmitChanges(SubmitCallBack, null);
            //전송한 파일 이름을 저장해 놓고
            captureFileName = name;
        }

        /// <summary>
        /// 세이브체인지 완료 콜백함수
        /// </summary>
        /// <param name="op"></param>
        private void SubmitCallBack(SubmitOperation op)
        {
            //에러가 있으면 에러 보여주고 끝~
            if (op.HasError)
            {
                MessageBox.Show(op.Error.Message);
                this.DialogResult = false;
                CaptureFileName = "";
            }
            else
            { //여긴 저장 완료~
                MessageBox.Show("작업을 완료했습니다.");
                this.DialogResult = true;
                CaptureFileName = captureFileName;
            }
            //팝업 화면 닫기
            this.Close();
        }

        private void OKButton_Click(object sender, RoutedEventArgs e)
        {
            //캡춰 시작
            _capture.CaptureImageAsync();
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = false;
        }

        private void StartButton_Click(object sender, RoutedEventArgs e)
        {
            Button btn = ((Button)sender);

            if (_capture != null)
            {
                //일단 동작 정지
                _capture.Stop();

                if (btn.Content.ToString() == "촬영시작")
                {
                    //기본 비디오 디바이스를 가지고옴
                    _capture.VideoCaptureDevice = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
                    //기본 오디오 디바이스를 가지고옴
                    _capture.AudioCaptureDevice = CaptureDeviceConfiguration.GetDefaultAudioCaptureDevice();

                    //비디오 브러쉬 만들고
                    VideoBrush videoBrush = new VideoBrush();
                    videoBrush.Stretch = Stretch.Uniform;
                    videoBrush.SetSource(_capture);
                    rectVideo.Fill = videoBrush;
                    // request user permission and display the capture
                    if (CaptureDeviceConfiguration.AllowedDeviceAccess || CaptureDeviceConfiguration.RequestDeviceAccess())
                    {
                        _capture.Start();
                        btn.Content = "촬영종료";
                    }
                }
                else
                {
                    btn.Content = "촬영시작";
                }
            }
        }
    }
}

 

3. 간단하게 만들려고 했는데..
일이 커져버렸다..;; 배보다 배꼽이 더 커진..흐흐..서버로 파일을 업로드 하는 방법도 이렇게 사용해서 올릴 수 있다. 어떤 파일이라도 스트림형태로 읽어 들인다면, 그 스트림의 버퍼를 WebFile 클래스에 쑤셔 넣어서 SubmitChange()만 호출 한다면 자동으로 올라간다..앞으로 실버라이트에서 WCF RIA Service를 사용한 완성된 파일 업로드 기능을 기대하며 이번 강좌를 마친다.

블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

실버라이트4.0 부터 기본적으로 웹켐과 오디오를 기본으로 사용할 수 있는 기능이 추가되어있다.
이 기능을 이용하는 방법을 알아보도록 하자..그런데, 왜 뜬금없이 웹켐에 대한 이야기를 하는 것일까? 다음 강좌에서 촬영한 사진을 WCF RIA Service로 서버로 전송하는 것을 올릴려고 하기 때문이다.


1. 화면보기

일단 기존 화면에 버튼을 하나 추가하고 버튼을 클릭하면 차일드 윈도우를 출력하고, 그 곳에서 웹켐 활성화를 시키고 사진 촬영을 하게 된다. 촬영시작을 누르면 위의 화면 처럼 실버라이트에서 카메라와 오디오에 엑세스를 허용할 것인지를 물어본다.(이렇게 나오도록 만든다고..몇시간 삽질을....나만 어렵게 적용했다는 생각이..쿨럭;;)


WebCam_Child.xaml

<controls:ChildWindow x:Class="SL4_RIA_Sample.WebCam_Child"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
           xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
           Width="400" Height="300"
           Title="WebCam_Child" Loaded="ChildWindow_Loaded">
    <Grid x:Name="LayoutRoot" Margin="2">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <!--이미지가 보이는 곳~-->
        <Rectangle Grid.Row="0" Name="rectVideo" Fill="Black" Margin="4" />

        <Button x:Name="CancelButton" Content="닫기" Click="CancelButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
        <Button x:Name="OKButton" Content="촬영" Click="OKButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" />
        <Button x:Name="StartButton" Content="촬영시작" Click="StartButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,158,0" Grid.Row="1" />
    </Grid>
</controls:ChildWindow>


WebCam_Child.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace SL4_RIA_Sample
{
    public partial class WebCam_Child : ChildWindow
    {
        private CaptureSource _capture;

        public WebCam_Child()
        {
            InitializeComponent();
        }

        private void ChildWindow_Loaded(object sender, RoutedEventArgs e)
        {
            //캡춰소스 인스턴스
            _capture = new CaptureSource();
            _capture.CaptureImageCompleted += new EventHandler<CaptureImageCompletedEventArgs>(_capture_CaptureImageCompleted);
        }

        void _capture_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
        {
            //이미지 캡춰가 완료되면 여기로
            WriteableBitmap wb = e.Result;
            //화면에 촬영된 이미지 보여주기
            if (_capture != null)
            {
                _capture.Stop();
                ImageBrush image = new ImageBrush();
                image.ImageSource = wb;
                rectVideo.Fill = image;
            }
        }

        private void OKButton_Click(object sender, RoutedEventArgs e)
        {
            //캡춰 시작
            _capture.CaptureImageAsync();
            //this.DialogResult = true;
        }

        private void CancelButton_Click(object sender, RoutedEventArgs e)
        {
            this.DialogResult = false;
        }

        private void StartButton_Click(object sender, RoutedEventArgs e)
        {
            Button btn = ((Button)sender);

            if (_capture != null)
            {
                //일단 동작 정지
                _capture.Stop();

                if (btn.Content.ToString() == "촬영시작")
                {
                    //기본 비디오 디바이스를 가지고옴
                    _capture.VideoCaptureDevice = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
                    //기본 오디오 디바이스를 가지고옴
                    _capture.AudioCaptureDevice = CaptureDeviceConfiguration.GetDefaultAudioCaptureDevice();

                    //비디오 브러쉬 만들고
                    VideoBrush videoBrush = new VideoBrush();
                    videoBrush.Stretch = Stretch.Uniform;
                    videoBrush.SetSource(_capture);

                    //xaml에 있는 사각형을 비디오브러쉬로 채운다
                    rectVideo.Fill = videoBrush;
                    // 사용자에게 인증을 받는 부분 - 이 부분은 폼로드 이벤트에 넣치 않는다. 별도의 이벤트 함수에서 처리하도록

                    // 해야 한다, 않그러면, 사용자에게 인증 받는 창이 뜨지를 안아서 한참을 헤메는 사태가 발생한다는..

                    if (CaptureDeviceConfiguration.AllowedDeviceAccess || CaptureDeviceConfiguration.RequestDeviceAccess())
                    {
                        _capture.Start();
                        btn.Content = "촬영종료";
                    }
                }
                else
                {
                    btn.Content = "촬영시작";
                }
            }
        }
    }
}

2. 다음에는
이 소스에 추가해서 촬영한 사진을 업로드 하는 것에 대해서 적어보도록 하겠다.

블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

간단하게 한가지만 추가할려고 한다.

대량의 데이터를 한번에 불러오는 경우 불러오는데 시간이 오래 걸리고, 그 데이터가 불러오기 전까지는 화면에 출력되는 내용이 없다. 그래서, 주로 사용하는 방식이 페이징 방식인데.. 페이징 방식의 경우에는 모든 데이터를 한번에 볼 수 없기 때문에..울나라 사람들이 조아하는 엑셀로 한방에 출력하기시에 문제가 있다..

그런 문제의 해답으로 점진적 로드 방식이 있는데, 한번에 20-40개 정도의 로우만을 여러번 불러오는 것이다. 그럼 일단 화면에 데이터가 출력되어있기 때문에 프로그램이 느리다는 이야기는 듣지 않을 것이고, 좀 시간을 두고 기다리면 모든 데이터가 어느세 꽉~ 차고, 마음도 한결 가벼워 질 것이다.


1. DomainService1.cs 수정
일단 WCF RIA에 함수를 하나 추가한다. 조건을 인수로 받아서 해당대는 데이터를 ID 순으로 소트해서 반환하는 함수이다.
여기서 중요한 사항은 꼭 orderby문이 포함되어 있어야 한다는 것이다.

//날짜를 인수로 받아서 해당 조건에 만족하는 데이터만 반환한다.
public IQueryable<Order> GetOrders_By_Condition(DateTime FromDate, DateTime ToDate)
{
    var query = from order in this.ObjectContext.Orders
                where order.Order_Date >= FromDate && order.Order_Date <= ToDate
                orderby order.Order_ID
                select order;

    return query.AsQueryable();
}

작성이 완료되었으면 웹 프로젝트를 빌드한다. 빌드를 해줘야 추가된 함수를 실버라이트에서 사용 할 수 있다.


2. SL4_RIA_Sample 프로젝트에 Silverlight User Control을 하나 추가한다.
이름은 Orders.xaml로 한다.
Data Sources 메뉴를 클릭한다.

Order에 콤보박스 다운 버튼을 클릭하면 위와 같이 나온다.(한번에 앙나오면 다시 컴파일 하고 다시 열어본다) 여기서 GetOrders_By_ConditionQuery를 선택하고 드레그 드롭으로 Orders.xaml에 컨트롤을 만들고, 약간의 작업을 더해주어 아래와 같은 화면을 만든다.

일단 컨트롤을 만들어서 화면에 출력될 수 있는 기본은 만들었고, 실버라이트가 시작되었을 때 맨 처음으로 이 화면을 사용하도록 수정한다.

App.xaml.cs
private void Application_Startup(object sender, StartupEventArgs e)
{
     this.RootVisual = new Orders();
}

실행해보자

데이터가 1991년 부터 1995년까지 있기 때문에..1년간의 날짜를 입력한 후 Load버튼을 눌러서 조회를 해봤다.
모든 데이터가 한번에 쫘악 펼쳐지며 보인다. 아주 만은 데이터는 아니라서 빠르게 표시가 된다. 그러나 날자를 1995년까지로 변경하고 조회하면 처음에 화면 뜨는 속도가 무척 느리다는 것을 알 수 있다.


3. 점진적 로드 방식으로 변경

Orders.xaml

//WCF RIA Service 데이터 컬렉션을 로드, 필터링, 그룹화 및 정렬하기 위한 개체

//http://msdn.microsoft.com/ko-kr/library/system.windows.controls.domaindatasource_members(v=vs.91).aspx


<riaControls:DomainDataSource AutoLoad="False"
                              d:DesignData="{d:DesignInstance my:Order, CreateList=true}" Height="0"
                              LoadedData="orderDomainDataSource_LoadedData"
                              Name="orderDomainDataSource" LoadSize="40" LoadInterval="00:00:04"
                              QueryName="GetOrders_By_ConditionQuery"
                              Width="0" Margin="0,0,400,29">
    <riaControls:DomainDataSource.DomainContext>
        <my:DomainService1 />
    </riaControls:DomainDataSource.DomainContext>
    <riaControls:DomainDataSource.QueryParameters>
        <riaControls:Parameter ParameterName="FromDate" Value="{Binding ElementName=fromDateTextBox, Path=Text}" />
        <riaControls:Parameter ParameterName="ToDate" Value="{Binding ElementName=toDateTextBox, Path=Text}" />
    </riaControls:DomainDataSource.QueryParameters>
</riaControls:DomainDataSource>

위의 LoadSize와 LoadInterval을 이용하면 한번에 몇개씩 몇 초후에 가지고 오는 지를 지정해서 순서대로 데이터를 가지고 올 수 있다.

(시작 - 스크롤 바의 크기를 보면 된다.)

(로딩 중 - 스크롤 바의 크기가 줄어 들었다)



4. WCF로 전송되는 데이터량 늘리기
한번에 전송되는 양이 생각보다 적어서, 좀더 늘릴 필요가 있을 때 사용한다.

Web.config

  <system.serviceModel>
    <domainServices>
      <endpoints>
        <add name="OData" type="System.ServiceModel.DomainServices.Hosting.ODataEndpointFactory, System.ServiceModel.DomainServices.Hosting.OData, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      </endpoints>
    </domainServices>
    <behaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="True" />
          <serviceDebug includeExceptionDetailInFaults="True" />
          <dataContractSerializer maxItemsInObjectGraph="655360" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
  </system.serviceModel>


5. 실버라이트는
스트림라인드 오퍼레이션이 기본이기 때문에, 모든 데이터를 한번에 다 불러와야 보여지는 그런 화면은 좋은 화면이 아니다. 개발시에는 꼭 참고 하기 바란다. 그리고, 추가 강의 요청이 없어서..그냥 생각 날때 마다 하나씩 올려 놓을려고 한다.(요청은 환영)

블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

지난번에 이어서 매우 간단한 CRUD 하는 방법을 설명하려고 한다. 최대한 간단하게 하기 위해서 타이틀리 커플드(tightly coupled)로 코딩을 하는 걸로 정했다. 나중에 문의가 들어온다면 MVVM을 적용한 루즐리 커플드(loosely coupled) 방식으로 변경하도록 하겠다.


1. 디자인 변경
화면의 상단에 공간을 좀 주고, 그 곳에 버튼을 3개 배치했다. 버튼의 이름은 Add, Update, Remove이다.


디자인을 변경하면 거의 50%는 한 셈이다. 이제 각 각의 버튼에 약간의 코딩을 하도록 하겠다.


2. CRUD 코딩하기
버튼을 더블클릭해서 이벤트를 만들어보자. Button_Click이란 이름의 이벤트가 하나 만들어지면 그걸 나머지 2개의 버튼에도 같이 붙여버린다. 그리고 코딩을 살짝한다.

using SL4_RIA_Sample.Web;


private void Button_Click(object sender, RoutedEventArgs e)
{
    Button btn = (Button)sender;
    //Order는 DTO(Data Transfer Object)로 WCF RIA에서 자동으로 만들어준 것이다. 이걸 사용하려면

    //SL4_RIA_Sample.Web를 써주기만 하면 된다.           

    Order order;

    switch (btn.Content.ToString())
    {
        case "Add":

            //새로운 레코드를 추가하기 위해서는 그냥 새로운 order를 생성해서 추가한 후에 SubmitChanges()만 호출하면 된다.
            order = new Order();
            orderDomainDataSource.DataView.Add(order);
            break;
        case "Update":

            //SubmitChanges()가 실행되면 Insert, Update, Delete가 모두 적용된다.

            //RejectChanges()는 지금까지 수정된 사항을 모두 취소한다.
            orderDomainDataSource.DomainContext.SubmitChanges();
            break;
        case "Remove":

            //삭제는 단지 DataView에서 삭제하려는 Order를 지우기만 하면 된다.
            if (orderDomainDataSource.DataView.CurrentItem != null)
            {
                order = orderDomainDataSource.DataView.CurrentItem as Order;
                orderDomainDataSource.DataView.Remove(order);
            }
            break;
    }
}

3. 변경된 내용 조회
리플로 문의 주신 내용인데.. 데이터베이스가 내가 변경하지 않고 다른 사람이나 프로시저에서 변경된 경우에 새로 조회를 해도 변경된 내용이 바로 반영이 앙된다는 것인데.. 이것은 LoadBehavior에 대한 처리를 해주면 쉽게 해결 할 수 있다.

WCF RIA Control은 LoadBehavior를 기본적으로 KeepCurrent로 지정되어있다.

LoadBehavior는 데이터를 로드했을때 현재 내가 가지고 있는 데이터와 새로 불러온 데이터를 어떻게 처리 할지를 지정하는 것으로 KeepCurrent(현재 가지고 있는 데이터 유지), MergeIntoCurrent(현재 가지고 있는 데이터를 새로 가지고 온 데이터와 머지), RefreshCurrent(현재 데이터를 새로운 데이터로 몽땅 교체)의 3가지 방법을 제공한다.

이 LoadBehavior를 지정하기 위해서는 몇가지 방법이 있는데, 현재 소스에는 DomainDataSource라는 컨트롤을 사용하고 있고 그 컨트롤에서 지정 방법은 LoadingData 이벤트를 추가하고,

private void orderDomainDataSource_LoadingData(object sender, LoadingDataEventArgs e)
{
    e.LoadBehavior = System.ServiceModel.DomainServices.Client.LoadBehavior.RefreshCurrent;
}

위와 같이 코딩을 해주면 된다.

4. 이번에는 여기 까지만 올리겠다.
추가로 요청이 올라오면 더 자세한 강좌를 올리도록 하겠다.


블로그 이미지

MVP kaki104

* Microsoft MVP - Windows Development 2014 ~ 2019 5ring * LINE : kaki104 * facebook : https://www.facebook.com/kaki104 https://www.facebook.com/groups/w10app/

티스토리 툴바