Command 에 인자로 두가지 항목을 던지고자 할때

이를 class 로 정의하여 CommandParameter 로 던질수 있다.

 

먼저 프로젝트에 Prism.Wpf Nuget Package 를 설치한다.

 

이제 Class 구조를 잡는다.

(string, object 로 구성)

[StringObject.cs]

namespace MultiValueConverterSample

{

    public class StringObject

    {

        /// <summary>

        /// 문자열 데이터입니다.

        /// </summary>

        public string StringData;

 

        /// <summary>

        /// 오브젝트 데이터입니다.

        /// </summary>

        public object ObjectData;

 

        /// <summary>

        /// 생성자입니다.

        /// </summary>

        /// <param name="stringData">문자열 데이터입니다.</param>

        /// <param name="objectData">오브젝트 데이터입니다.</param>

        public StringObject(string stringData, object objectData)

        {

            this.StringData = stringData;

            this.ObjectData = objectData;

        }

    }

}

 

 

IMultiValueConverter 를 정의한다.

(화면에서 처리된 두개의 인자를 가지고 위에서 만들 Class 구조로 변환해 리턴)

[StringObjectConverter.cs]

using System;

using System.Globalization;

using System.Windows.Data;

 

namespace MultiValueConverterSample

{

    public class StringObjectConverter : IMultiValueConverter

    {

        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)

        {

            return new StringObject(values[0].ToString(), values[1]);

        }

 

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)

        {

            throw new NotImplementedException();

        }

    }

}

 

 

화면에 아래 처럼 구성한다.

(조회 버튼을 클릭하면 StringObjectCommand 를 실행해

입력한 값을 string 으로 버튼 객체를 object 로 하여 CommandParameter 로 사용한다.)

[MainWindow.xaml]

<Window x:Class="MultiValueConverterSample.MainWindow"

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

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

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

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        xmlns:local="clr-namespace:MultiValueConverterSample"

        mc:Ignorable="d"

        Title="MainWindow" Height="450" Width="800">

    <Window.Resources>

        <local:StringObjectConverter x:Key="StringObjectConverter" />

    </Window.Resources>

    <Grid>

        <StackPanel>

            <TextBox Name="contentTextBlock" Width="100" Height="30" Margin="10"/>

            <Button Name="searchButton" Content="조회" Margin="10" Height="30" Width="100" Command="{Binding StringObjectCommand}">

                <Button.CommandParameter>

                    <MultiBinding Converter="{StaticResource StringObjectConverter}">

                        <Binding ElementName="contentTextBlock" Path="Text" />

                        <Binding ElementName="searchButton" />

                    </MultiBinding>

                </Button.CommandParameter>

            </Button>

        </StackPanel>

    </Grid>

</Window>

 

 

코드비하인드 단에서는 아래 처럼 처리한다.

(반드시 this.DataContext = this; 을 해야 Command 가 처리된다.)

[MainWindow.xaml.cs]

using Prism.Commands;

 

using System.Windows;

using System.Windows.Input;

 

namespace MultiValueConverterSample

{

    /// <summary>

    /// MainWindow.xaml에 대한 상호 작용 논리

    /// </summary>

    public partial class MainWindow : Window

    {

        #region StringObjectCommand

 

        /// <summary>

        /// StringObjectCommand

        /// </summary>

        public ICommand StringObjectCommand

        {

            get

            {

                if (stringObjectCommand == null)

                {

                    stringObjectCommand = new DelegateCommand<StringObject>((stringObject) =>

                    {

                        string stringData = stringObject.StringData;

                        object objectData = stringObject.ObjectData;

                    });

                }

                return stringObjectCommand;

            }

        }

        private DelegateCommand<StringObject> stringObjectCommand;

 

        #endregion

 

        public MainWindow()

        {

            InitializeComponent();

 

            this.DataContext = this;

        }

    }

}

 

 

 

조회 버튼을 누르게 되면 Command 에서 아래 처럼 해당 항목이 넘어온것을 확인할 수 있다.

 

 

 

ContextMenu 의 Item 을 선택한 경우 ContextMenu 가 닫히는데

만약 체크 박스가 있는 경우 이를 유지하고 싶을 때가 있다.

이때 MenuItem 의 StaysOpenOnClick 을 true 로 주게 되면 ContextMenu 가 닫히지 않고

클릭 이벤트가 수행된다.


                <ContextMenu>

                    <MenuItem Header="첫번째" IsCheckable="True" StaysOpenOnClick="True"/>

                </ContextMenu>



아래 예시는 각기 다른 4가지 이미지를 4분면에 그리는 코드입니다.

 

4분면에 이미지를 하나씩 그리는 예시입니다.

처음 이미지의 크기에 따라 나머지 이미지 들도 크기가 지정되며

바둑판모양으로 이미지가 달라 붙습니다.

 

        private void MergeImage(string path1, string path2, string path3, string path4, string outputFilePath)

        {

            // 이미지를 로드합니다.

            BitmapFrame frame1 = BitmapDecoder.Create(new Uri(path1), BitmapCreateOptions.None, BitmapCacheOption.OnLoad).Frames.First();

            BitmapFrame frame2 = BitmapDecoder.Create(new Uri(path2), BitmapCreateOptions.None, BitmapCacheOption.OnLoad).Frames.First();

            BitmapFrame frame3 = BitmapDecoder.Create(new Uri(path3), BitmapCreateOptions.None, BitmapCacheOption.OnLoad).Frames.First();

            BitmapFrame frame4 = BitmapDecoder.Create(new Uri(path4), BitmapCreateOptions.None, BitmapCacheOption.OnLoad).Frames.First();

 

            // 첫번째이미지 기준으로 그려질 이미지의 크기를 정의합니다.

            int imageWidth = frame1.PixelWidth;

            int imageHeight = frame1.PixelHeight;

 

            // DrawingVisual 에 전체 4분면에서 각 분면 이미지를 그립니다.

            DrawingVisual drawingVisual = new DrawingVisual();

            using (DrawingContext drawingContext = drawingVisual.RenderOpen())

            {

                drawingContext.DrawImage(frame1, new Rect(0, 0, imageWidth, imageHeight));

                drawingContext.DrawImage(frame2, new Rect(imageWidth, 0, imageWidth, imageHeight));

                drawingContext.DrawImage(frame3, new Rect(0, imageHeight, imageWidth, imageHeight));

                drawingContext.DrawImage(frame4, new Rect(imageWidth, imageHeight, imageWidth, imageHeight));

            }

 

            // RenderTargetBitmap 을 사용해 DrawingVisual 을 BitmapSource 객체로 변환합니다.

            RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap(imageWidth * 2, imageHeight * 2, 96, 96, PixelFormats.Pbgra32);

            renderTargetBitmap.Render(drawingVisual);

 

            // BitmapSource 를 PngBitmapEncoder 를 사용해 frame 에 추가합니다.

            PngBitmapEncoder encoder = new PngBitmapEncoder();

            encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));

 

            // 이미지를 저장합니다.

            using (Stream stream = File.Create(outputFilePath))

            {

                encoder.Save(stream);

            }

        }

 

* ico 파일 지정도 가능

 

결과

Image 를 사용할때 코드 상에서 이미지 경로를 지정 할때

아래 처럼 사용합니다.

 

 

BitmapImage result = new BitmapImage();

result.BeginInit();

result.UriSource = new Uri(@"C:\Users\my\Desktop\100.PNG");

result.EndInit();

 

// Image control

this.resultImage.Source = result;

 

 

1. Self

{Binding RelativeSource={RelativeSource Self}, Path=Height}

: 자기 자신을 참조하는 것으로 위 내용은 자기 자신의 Height 값을 바인딩합니다.

 

2. FindAncestor

{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Border}, AncestorLevel=2},Path=Name}

: 부모를 찾아 바인딩하는 것으로 위 내용은 부모 중 Border 타입을 찾는데 2번째 부모를 찾아 Name 값을 바인딩합니다. (1이면 Border 타입중 바로 자기 상위 부모를 찾습니다.)

 

3. TemplatedParent

<Ellipse Height="100" Width="150" Fill="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background}"/>

: 위 항목이 Style 에 정의 되어있는 경우 해당 Style 을 사용하는 항목의 요소를 변경한다.

  위내용은 원을 그릴때 해당 Style 를 사용하는 컨트롤의 BackGround 색으로 원을 체웁니다.

 

4. PreviousData

{Binding RelativeSource={RelativeSource PreviousData},Path=Value}

: 이전값을 가지는 것으로 위 내용은 이전 값의 Value 값을 바인딩 합니다.

 

 

참고 : https://www.c-sharpcorner.com/UploadFile/yougerthen/relativesources-in-wpf/

 

아래는 백업 용도 ---

 

A lot of articles those are talking about binding and sources, and how to bind properties each other using StaticResources, DynamicResources, although you can find information about the RelativeSource and its use cases but not with more details even in Microsoft documentations. In this article, I will expose the use cases of the RelativeSources in WPF.

The RelativeSource is a markup extension that is used in particular binding cases when we try to bind a property of a given object to another property of the object itself, when we try to bind a property of a object to another one of its relative parents, when binding a dependency property value to a piece of XAML in case of custom control development and finally in case of using a differential of a series of a bound data. All of those situations are expressed as relative source modes. I will expose all of those cases one by one.

1. Mode Self:

Imagine this case, a rectangle that we want that its height is always equal to its width, a square let's say. We can do this using the element name

<Rectangle Fill="Red" Name="rectangle"
                   
 Height="100" Stroke="Black"
                   
 Canvas.Top="100" Canvas.Left="100"
                    Width="{Binding ElementName=rectangle,
                    Path=Height}"/>

But in this above case we are obliged to indicate the name of the binding object, namely the rectangle. We can reach the same purpose differently using the RelativeSource

<Rectangle Fill="Red" Height="100"
                  
 Stroke="Black"
                  
 Width="{Binding RelativeSource={RelativeSource Self},
                   Path=Height}"/>

For that case we are not obliged to mention the name of the binding object and the Width will be always equal to the Height whenever the height is changed.

If you want to parameter the Width to be the half of the height then you can do this by adding a converter to the Binding markup extension.
Let's imagine another case now:

 <TextBlock Width="{Binding RelativeSource={RelativeSource Self},
                   Path=Parent.ActualWidth}"/>

The above case is used to tie a given property of a given element to one of its direct parent ones as this element holds a property that is called Parent. This leads us to another relative source mode which is the FindAncestor one.

2. Mode FindAncestor

In this case, a property of a given element will be tied to one of its parents, Of Corse. The main difference with the above case is the fact that, it's up to you to determine the ancestor type and the ancestor rank in the hierarchy to tie the property. By the way try to play with this piece of XAML

<Canvas Name="Parent0">
        <Border Name="Parent1"
                 Width="{Binding RelativeSource={RelativeSource Self},
                 Path=Parent.ActualWidth}"
                 Height="{Binding RelativeSource={RelativeSource Self},
                 Path=Parent.ActualHeight}">
            <Canvas Name="Parent2">
                <Border Name="Parent3"
                Width="{Binding RelativeSource={RelativeSource Self},
               Path=Parent.ActualWidth}"
               Height="{Binding RelativeSource={RelativeSource Self},
                  Path=Parent.ActualHeight}">
                   <Canvas Name="Parent4">
                   <
TextBlock FontSize="16"
                   Margin="5" Text="Display the name of the ancestor"/>
                   <TextBlock FontSize="16"
                     
Margin="50"
                
Text="{Binding RelativeSource={RelativeSource 
                           FindAncestor
,
                           
AncestorType={x:Type Border},
                           AncestorLevel=2},Path=Name}"
                           
Width="200"/>
                    </Canvas>
                </Border>
            </Canvas>
        </Border>
    </Canvas>

The above situation is of two TextBlock elements those are embedded within a series of borders and canvas elements those represent their hierarchical parents. The second TextBlock will display the name of the given parent at the relative source level.

So try to change AncestorLevel=2 to AncestorLevel=1 and see what happens. Then try to change the type of the ancestor from AncestorType=Border to AncestorType=Canvas and see what's happens.

The displayed text will change according to the Ancestor type and level. Then what's happen if the ancestor level is not suitable to the ancestor type? This is a good question, I know that you're about to ask it. The response is no exceptions will be thrown and nothings will be displayed at the TextBlock level.

3. TemplatedParent

This mode enables tie a given ControlTemplate property to a property of the control that the ControlTemplate is applied to. To well understand the issue here is an example bellow

<Window.Resources>
    <ControlTemplate x:Key="template">
            <Canvas>
                <Canvas.RenderTransform>
                    <RotateTransform Angle="20"/>
                    </Canvas.RenderTransform>
                <Ellipse Height="100" Width="150"
                    
 Fill="{Binding
               
 RelativeSource={RelativeSource TemplatedParent},
                Path=Background}">

                  </Ellipse>
                <ContentPresenter Margin="35"
                      Content
="{Binding RelativeSource={RelativeSource 
                      TemplatedParent
},Path=Content}"/>
            </Canvas>
        </ControlTemplate>
    </Window.Resources>
        <Canvas Name="Parent0">
        <Button   Margin="50"
                  Template
="{StaticResource template}" Height="0"
                  Canvas.Left
="0" Canvas.Top="0" Width="0">
            <TextBlock FontSize="22">Click me</TextBlock>
        </Button>
    </Canvas>

If I want to apply the properties of a given control to its control template then I can use the TemplatedParent mode. There is also a similar one to this markup extension which is the TemplateBinding which is a kind of short hand of the first one, but the TemplateBinding is evaluated at compile time at the contrast of the TemplatedParent which is evaluated just after the first run time. As you can remark in the bellow figure, the background and the content are applied from within the button to the control template.



4. PreviousData

This is the most ambiguous and the less used mode of the RelativeSource, I mean the PreviousData one. The PreviousData is used for particular cases. Its purpose is to tie the given property to another property with a particular assignment; I mean it assigns the previous value of the property to the bound one. In other word, if you have a TextBox that has a text property and another control that has a value property that holds data. Say that value is actually 5 and it was 3 just before. The 3 is assigned to the text property of the TextBox and not 5. This leads to the idea that this kind of RelativeSource is frequently used with the items controls.

To understand the phenomenon of the RelativeSource let's expose this sample. I will add an ItemsControl into the scene and I will aliment it from a custom collection

<Grid>
    <ItemsControl></ItemsControl>
</
Grid>

This ItemsControl is alimented using this collection:

public class Items : ObservableCollection<Item>
    {
        
public Items()
        {
            Add(
new Item { Value = 80.23 });
            Add(
new Item { Value = 126.17 });
            Add(
new Item { Value = 130.21 });
            Add(
new Item { Value = 115.28 });
            Add(
new Item { Value = 131.21 });
            Add(
new Item { Value = 135.22 });
            Add(
new Item { Value = 120.27 });
            Add(
new Item { Value = 110.25 });
            Add(
new Item { Value = 90.20 });
        }
    }


It's an ObservableCollection of type Item that I had developed and that holds a simple property which is called Value, it is of type double.

 
public class Item :INotifyPropertyChanged
    {
        
private double _value;

        public double Value
        {
            
get { return _value; }
            
set { _value = value; OnPropertyChanged("Value"); }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

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

Now, to bind the 
ItemsControl to the collection data, I will set the DataContext property of the whole window to the collection at the Window constructor level.

 public Window1()

        {

            InitializeComponent();

            this.DataContext = new Items();

        }

And then I'll specify the binding of the ItemsControl

ItemsControl ItemsSource="{Binding}" Margin="10"

Then the result will be like this. I mean not presentable.



Therefore, we have to apply some features to enhance the visual of that representation.

<ItemsControl ItemsSource="{Binding}" Margin="10">

        <ItemsControl.ItemsPanel>
           <ItemsPanelTemplate>
              <StackPanel Orientation="Horizontal"/>
           </ItemsPanelTemplate>
           </ItemsControl.ItemsPanel>
       <ItemsControl.ItemTemplate>
          <DataTemplate>
              <StackPanel>
             <Border CornerRadius="3" BorderThickness="3"
                Width
="80" Height="{Binding Value}"
                            Margin
="0,0,35,0" 
                           
 BorderBrush="Violet" 
                            Background
="BlueViolet">
                            <TextBlock Text="{Binding Value}"
                               FontWeight
="bold"
                               VerticalAlignment
="Center"
                               
HorizontalAlignment="Center" 
                               Foreground
="Wheat">
                        <TextBlock.RenderTransform>
                  <TransformGroup>
                            <ScaleTransform ScaleY="-1"/>
                 
</TransformGroup>
                  </TextBlock.RenderTransform>
                  </TextBlock>
             </Border>
         </StackPanel>
      </DataTemplate>
   </ItemsControl.ItemTemplate>
   <
ItemsControl.RenderTransform>
       <TransformGroup>
         <ScaleTransform ScaleY="-1"/>
         <TranslateTransform Y="250"/>
       </TransformGroup>
   </ItemsControl.RenderTransform>
 </ItemsControl>


Shortly, I will describe the above XAML. First, the ItemsPanel will arrange the items within an horizontal StackPanel for more information about the ItemsPanel please refer to this link

http://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.itemspanel.aspx


Second, the DataTemplate is used to present the data as a border; the border height is bound to the Value of the item class to reflect the Values that the collection holds. The same border includes a TextBlock that displays the Value of the Item object.

For more information about te DateTemplate please refer to this MSDN link

http://msdn.microsoft.com/en-us/library/system.windows.datatemplate.aspx



The RenderTransform is used to emphasize the position of the items in the scene. For more information about transformation please refer to this MSDN link

http://msdn.microsoft.com/en-us/library/system.windows.media.transform.aspx


The result of the presentation will be as follow



Now, the main purpose of this demo is to show the characteristic of the RelativeSource.PreviousData mode.

The idea consists of adding a TextBox and tie the Text property to the Value of the previous border in the items' list. Something that seems to be as the bellow representation



As you can note, each TextBlock represents the previous value that the previous item holds. This is in fact the magic of the PreviousData of the RelativeSource mode.

The idea is to add the TextBlock to the DataTemplate as Follow

<TextBlock FontSize="14" FontWeight="bold" Margin="20"
            
Text="{Binding RelativeSource={RelativeSource PreviousData},
                   Path
=Value}">
                          <TextBlock.RenderTransform>
                              <ScaleTransform ScaleY="-1"/>
                          </TextBlock.RenderTransform>   
</TextBlock>

Then the whole picture will be

<Grid>
    <ItemsControl ItemsSource="{Binding}" Margin="10">
        <ItemsControl.RenderTransform>
            <TransformGroup>
                <ScaleTransform ScaleY="-1"/>
                <TranslateTransform Y="250"/>
            </TransformGroup>
        </ItemsControl.RenderTransform>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock FontSize="14" FontWeight="bold"
                           Margin
="20"
                           
Text="{Binding
                           RelativeSource
={RelativeSource PreviousData},
                                               Path
=Value}">
                      <TextBlock.RenderTransform>
                          <ScaleTransform ScaleY="-1"/>
                      </TextBlock.RenderTransform>   
                    
</TextBlock>
                    <Border CornerRadius="3" BorderThickness="3"
                           Width
="80" Height="{Binding Value}"
                           Margin
="0,0,35,0" 
                       
 BorderBrush="Violet" Background="BlueViolet">
                        <TextBlock Text="{Binding Value}"
                           FontWeight
="bold" VerticalAlignment="Center"
                                  
 HorizontalAlignment="Center"
                                   Foreground
="Wheat">
                        <TextBlock.RenderTransform>
                            <TransformGroup>
                        <ScaleTransform ScaleY="-1"/>
                        
</TransformGroup>
                        </TextBlock.RenderTransform>
                        </TextBlock>
                    </Border>
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</
Grid>

Of Course, we could do more that this using RelativeSource.PreviousData but this will be enough to have a general consistent idea about that mode. That's all

DrawingBrush 를 이용하여 체스판을 만들어 봅니다.

체스판의 한 조각을 만들어 이어 붙이는 예제입니다.

 

한조각은 아래 그림과 같으며

 

 

DrawingBrush 의 Viewport 와 TileMode 를 이용해 이어붙여 체스판을 완성합니다.

 

MainWindow.xaml.cs

        private void CreateChessBoardWithDrawingBrush()

        {

            // 반복하여 그려질 사각형의 바탕이 되는 흰색배경의 사각형을 만듭니다.

            GeometryDrawing whiteRectDrawing = new GeometryDrawing(Brushes.White, null, new RectangleGeometry(new Rect(0, 0, 400, 400)));

 

            // 검은색으로 채울 사각형을 정의합니다.

            GeometryGroup blackGeometryGroup = new GeometryGroup();

            blackGeometryGroup.Children.Add(new RectangleGeometry(new Rect(0, 0, 200, 200)));

            blackGeometryGroup.Children.Add(new RectangleGeometry(new Rect(200, 200, 200, 200)));

 

            // 정의한 사격형을 검은색으로 체웁니다.

            GeometryDrawing blackRectDrawing = new GeometryDrawing(new SolidColorBrush(Colors.Black), null, blackGeometryGroup);

 

            // 배경에 해당하는 흰색 사각형과 체크표시할 검은색 사각형을 그룹으로 묶습니다.

            DrawingGroup checkersDrawingGroup = new DrawingGroup();

            checkersDrawingGroup.Children.Add(whiteRectDrawing);

            checkersDrawingGroup.Children.Add(blackRectDrawing);

 

            // DrawingGroup 를 요소로 가지는 DrawingBrush 를 정의합니다.

            DrawingBrush chessBrush = new DrawingBrush() { Drawing = checkersDrawingGroup };

 

            // DrawingGroup 의 Viewport 와  TileMode 를 정의합니다.

            chessBrush.Viewport = new Rect(0, 0, 0.25, 0.25); // 1 기준으로 0.25 인경우 4칸.4줄로 나눠집니다.

            chessBrush.TileMode = TileMode.Tile;

 

            // 큰 사각형을 정의하여 DrawingBrush 로 내부를 그리도록 합니다.

            Rectangle chessRectangle = new Rectangle();

            chessRectangle.Width = 300;

            chessRectangle.Height = 300;

            chessRectangle.Fill = chessBrush;

 

            Content = chessRectangle;

        }

 

결과

 

 

 

 

DrawingBrush 로 정의된 DrawingGroup 을 그리게되는데

Viewport 로 화면에 표시되는 비율을 정의할 수 있으며

TileMode 를 이용해 어떻게 표시될지를 정의할 수있습니다.

 

아래 코드와 같이 사각형 안에 단순히 X 표시를 하는 Geometry 를 정의하여 도형을 체울 수 있습니다.

                <Rectangle Width="100" Height="100" Stroke="Black" StrokeThickness="1">

                    <Rectangle.Fill>

                        <DrawingBrush >

                            <DrawingBrush.Drawing>

                                <DrawingGroup>

                                    <DrawingGroup.Children>

                                        <GeometryDrawing Geometry="M0,0.1 L0.1,0 1,0.9, 0.9,1z" Brush="Blue" />

                                        <GeometryDrawing Geometry="M0.9,0 L1,0.1 0.1,1 0,0.9z" Brush="Blue" />

                                    </DrawingGroup.Children>

                                </DrawingGroup>

                            </DrawingBrush.Drawing>

                        </DrawingBrush>

                    </Rectangle.Fill>

                </Rectangle>

 

 

이를 Viewport 를 이용하여 비율을 조정할 수 있습니다.

(참고 : https://docs.microsoft.com/ko-kr/dotnet/api/system.windows.media.tilebrush.viewport?view=netcore-3.1)

 

                <Rectangle Width="100" Height="100" Stroke="Black" StrokeThickness="1">

                    <Rectangle.Fill>

                        <DrawingBrush Viewport="0,0,0.5,0.5">

                            <DrawingBrush.Drawing>

                                <DrawingGroup>

                                    <DrawingGroup.Children>

                                        <GeometryDrawing Geometry="M0,0.1 L0.1,0 1,0.9, 0.9,1z" Brush="Blue" />

                                        <GeometryDrawing Geometry="M0.9,0 L1,0.1 0.1,1 0,0.9z" Brush="Blue" />

                                    </DrawingGroup.Children>

                                </DrawingGroup>

                            </DrawingBrush.Drawing>

                        </DrawingBrush>

                    </Rectangle.Fill>

                </Rectangle>

 

값을 보면 알겠지만 ViewPort 의 뒤 두자리 수가 가로,세로 비율이라고 보면 됩니다. 0.5 이므로 반으로 줄어들어 표시가 됩니다.

 

 

이제 여기서 TileMode 를 이용해 Tile 로 정의하면 타일형태로 체워지게됩니다.

(참고 : https://docs.microsoft.com/ko-kr/dotnet/api/system.windows.media.tilebrush.tilemode?view=netcore-3.1)

 

                <Rectangle Width="100" Height="100" Stroke="Black" StrokeThickness="1">

                    <Rectangle.Fill>

                        <DrawingBrush Viewport="0,0,0.5,0.5" TileMode="Tile">

                            <DrawingBrush.Drawing>

                                <DrawingGroup>

                                    <DrawingGroup.Children>

                                        <GeometryDrawing Geometry="M0,0.1 L0.1,0 1,0.9, 0.9,1z" Brush="Blue" />                                        <GeometryDrawing Geometry="M0.9,0 L1,0.1 0.1,1 0,0.9z" Brush="Blue" />

                                    </DrawingGroup.Children>

                                </DrawingGroup>

                            </DrawingBrush.Drawing>

                        </DrawingBrush>

                    </Rectangle.Fill>

                </Rectangle>

 

 

 

아래는 위 설명한 내용을 바탕으로 만든 예시입니다.

 

<Window x:Class="DrawingBrushExample.MainWindow"

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

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

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

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        mc:Ignorable="d"

        Title="MainWindow" Height="450" Width="800">

    <StackPanel>

        <StackPanel Margin="10">

            <StackPanel Orientation="Horizontal">

                <Rectangle Width="100" Height="100" Stroke="Black" StrokeThickness="1">

                    <Rectangle.Fill>

                        <DrawingBrush >

                            <DrawingBrush.Drawing>

                                <DrawingGroup>

                                    <DrawingGroup.Children>

                                        <GeometryDrawing Geometry="M0,0.1 L0.1,0 1,0.9, 0.9,1z" Brush="Blue" />

                                        <GeometryDrawing Geometry="M0.9,0 L1,0.1 0.1,1 0,0.9z" Brush="Blue" />

                                    </DrawingGroup.Children>

                                </DrawingGroup>

                            </DrawingBrush.Drawing>

                        </DrawingBrush>

                    </Rectangle.Fill>

                </Rectangle>

 

                <Rectangle Width="100" Height="100" Stroke="Black" StrokeThickness="1">

                    <Rectangle.Fill>

                        <DrawingBrush Viewport="0,0,0.5,0.5" TileMode="Tile">

                            <DrawingBrush.Drawing>

                                <DrawingGroup>

                                    <DrawingGroup.Children>

                                        <GeometryDrawing Geometry="M0,0.1 L0.1,0 1,0.9, 0.9,1z" Brush="Blue" />

                                        <GeometryDrawing Geometry="M0.9,0 L1,0.1 0.1,1 0,0.9z" Brush="Blue" />

                                    </DrawingGroup.Children>

                                </DrawingGroup>

                            </DrawingBrush.Drawing>

                        </DrawingBrush>

                    </Rectangle.Fill>

                </Rectangle>

 

                <Rectangle Width="100" Height="100" Stroke="Black" StrokeThickness="1">

                    <Rectangle.Fill>

                        <DrawingBrush Viewport="0,0,0.1,0.1" TileMode="Tile">

                            <DrawingBrush.Drawing>

                                <DrawingGroup>

                                    <DrawingGroup.Children>

                                        <GeometryDrawing Geometry="M0,0.1 L0.1,0 1,0.9, 0.9,1z" Brush="Blue" />

                                        <GeometryDrawing Geometry="M0.9,0 L1,0.1 0.1,1 0,0.9z" Brush="Blue" />

                                    </DrawingGroup.Children>

                                </DrawingGroup>

                            </DrawingBrush.Drawing>

                        </DrawingBrush>

                    </Rectangle.Fill>

                </Rectangle>

 

            </StackPanel>

        </StackPanel>

    </StackPanel>

</Window>

 

 

마우스 드래그시 사각형을 그리고 사각형 주변으로 길이를 표시합니다.

 

MainWindow.xaml

<Window x:Class="WpfApp.MainWindow"

        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:local="clr-namespace:WpfApp"

        mc:Ignorable="d"

        Title="MainWindow" Height="450" Width="800" Background="Gray">

    <Grid Background="#01000000">

        <Canvas IsHitTestVisible="False" IsEnabled="False">

            <!-- Rectangle -->

            <ContentControl x:Name="RectAdder" Visibility="Collapsed">

                <ContentControl.ContentTemplate>

                    <DataTemplate>

                        <Grid >

                            <Rectangle

                                x:Name="Rect" SnapsToDevicePixels="True"

                                Fill="#770000FF" Stroke="White" StrokeThickness="1" StrokeDashArray="1 1 1 3" />

                            <!--위쪽-->

                            <Grid x:Name="DrawingHSizeAdorner">

                                <Rectangle

                                    Width="1" Height="15" Fill="White" SnapsToDevicePixels="True"

                                    IsHitTestVisible="False" IsEnabled="False"

                                    HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,-18,0,0"

                                    Tag="TopLeft"/>

                                <Rectangle

                                    Height="1" Width="{Binding ActualWidth, ElementName=Rect}"

                                    Fill="White" SnapsToDevicePixels="True"

                                    IsHitTestVisible="False" IsEnabled="False"

                                    HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,-10,0,0"

                                    Tag="TopCenter"/>

                                <Rectangle

                                    Width="1" Height="15" Fill="White" SnapsToDevicePixels="True"

                                    IsHitTestVisible="False" IsEnabled="False"

                                    HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,-18,0,0"

                                    Tag="TopRight"/>

                                <TextBlock

                                    Text="{Binding ActualWidth, ElementName=Rect, StringFormat='0.00 mm'}" Height="15" Width="{Binding ActualWidth, ElementName=Rect}"

                                    SnapsToDevicePixels="True" IsHitTestVisible="False" IsEnabled="False"

                                    HorizontalAlignment="Center" VerticalAlignment="Top"

                                    TextAlignment="Center" Foreground="White" FontSize="9"

                                    Margin="0,-25,0,0"

                                    ClipToBounds ="True"/>

                            </Grid>

                            <!--아래쪽-->

                            <Grid x:Name="DrawingBHSizeAdorner" >

                                <Rectangle

                                    Width="1" Height="15" Fill="White" SnapsToDevicePixels="True"

                                    IsHitTestVisible="False" IsEnabled="False"

                                    HorizontalAlignment="Left" VerticalAlignment="Bottom"

                                    Margin="0,0,0,-18"

                                    Tag="BottomLeft"/>

                                <Rectangle

                                    Height="1" Width="{Binding ActualWidth, ElementName=Rect}"

                                    Fill="White" SnapsToDevicePixels="True"

                                    IsHitTestVisible="False" IsEnabled="False"

                                    HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,-10"

                                    Tag="BottomCenter"/>

                                <Rectangle

                                    Width="1" Height="15" Fill="White" SnapsToDevicePixels="True"

                                    IsHitTestVisible="False" IsEnabled="False"

                                    HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,0,-18"

                                    Tag="BottomRight"/>

                                <TextBlock

                                    Text="{Binding ActualWidth, ElementName=Rect, StringFormat='0.00 mm'}" Height="15" Width="{Binding ActualWidth, ElementName=Rect}"

                                    SnapsToDevicePixels="True" IsHitTestVisible="False" IsEnabled="False"

                                    HorizontalAlignment="Center" VerticalAlignment="Bottom"

                                    TextAlignment="Center" Foreground="White" FontSize="9"

                                    Margin="0,0,0,-25"

                                    ClipToBounds ="True"/>

                            </Grid>

                            <!--왼쪽-->

                            <Grid x:Name="DrawingLVSizeAdorner" Margin="10,0,0,0">

                                <Rectangle Width="15" Height="1" Fill="White" SnapsToDevicePixels="True"

                                           IsHitTestVisible="False" IsEnabled="False"

                                           HorizontalAlignment="Left"

                                           VerticalAlignment="Top"

                                           Margin="-27,0,0,0"

                                           Tag="LeftTop"/>

                                <Rectangle Height="{Binding ActualHeight, ElementName=Rect}" Width="1" Fill="White" SnapsToDevicePixels="True"

                                           IsHitTestVisible="False" IsEnabled="False"

                                           HorizontalAlignment="Left" VerticalAlignment="Center" Margin="-20,0,0,0"

                                           Tag="LeftCenter"/>

                                <Rectangle Width="15" Height="1" Fill="White" SnapsToDevicePixels="True"

                                           IsHitTestVisible="False" IsEnabled="False"

                                           HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="-27,0,0,0"

                                           Tag="LeftBottom"/>

                                <StackPanel Margin="-35,70,0,0" HorizontalAlignment="Left" Height="25"

                                            SnapsToDevicePixels="True" IsHitTestVisible="False" IsEnabled="False"

                                            VerticalAlignment="Center">

                                    <StackPanel.RenderTransform>

                                        <RotateTransform Angle="270"/>

                                    </StackPanel.RenderTransform>

                                    <TextBlock Text="{Binding ActualHeight, ElementName=Rect, StringFormat='0.00 mm'}" Height="25" Foreground="White" FontSize="9"/>

                                </StackPanel>

                            </Grid>

                            <!--오른쪽-->

                            <Grid x:Name="DrawingVSizeAdorner" Margin="10,0,0,0">

                                <Rectangle

                                    Width="15" Height="1" Fill="White" SnapsToDevicePixels="True"

                                    IsHitTestVisible="False" IsEnabled="False"

                                    HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,0,-18,0"

                                    Tag="RightTop"/>

                                <Rectangle Height="{Binding ActualHeight, ElementName=Rect}" Width="1" Fill="White" SnapsToDevicePixels="True"

                                           IsHitTestVisible="False" IsEnabled="False"

                                           HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,-11,0"

                                           Tag="RightCenter"/>

                                <Rectangle Width="15" Height="1" Fill="White" SnapsToDevicePixels="True"

                                           IsHitTestVisible="False" IsEnabled="False"

                                           HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,-18,0"

                                           Tag="RightBottom"/>

                                <StackPanel Margin="0,0,-70,0" HorizontalAlignment="Right" Height="25"

                                            SnapsToDevicePixels="True" IsHitTestVisible="False" IsEnabled="False"

                                            VerticalAlignment="Center">

                                    <StackPanel.RenderTransform>

                                        <RotateTransform Angle="90"/>

                                    </StackPanel.RenderTransform>

                                    <TextBlock Text="{Binding ActualHeight, ElementName=Rect, StringFormat='0.00 mm'}" Height="25" Foreground="White" FontSize="9"/>

                                </StackPanel>

                            </Grid>

                            <TextBlock Text="+" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White" />

                        </Grid>

                    </DataTemplate>

                </ContentControl.ContentTemplate>

            </ContentControl>

        </Canvas>

    </Grid>

</Window>

 

 

MainWindow.xaml.cs

using System;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Input;

 

namespace WpfApp

{

    /// <summary>

    /// MainWindow.xaml에 대한 상호 작용 논리

    /// </summary>

    public partial class MainWindow : Window

    {

        /// <summary>

        /// 좌측 마우스 클릭 시 포인트 위치입니다.

        /// </summary>

        Point? downPoint;

 

        /// <summary>

        /// 그려질 사각형입니다.

        /// </summary>

        Rect addRect;

 

        public MainWindow()

        {

            InitializeComponent();

        }

 

        /// <summary>

        /// 마우스 좌측 버튼 클릭 시 이벤트입니다.

        /// </summary>

        /// <param name="e"></param>

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)

        {

            var pos = e.GetPosition(this);

            downPoint = pos;

 

            this.CaptureMouse();

        }

 

        /// <summary>

        /// 마우스 이동 시 이벤트입니다.

        /// </summary>

        /// <param name="e"></param>

        protected override void OnPreviewMouseMove(MouseEventArgs e)

        {

            base.OnPreviewMouseMove(e);

 

            if (Mouse.LeftButton == MouseButtonState.Pressed)

            {

                var pos = e.GetPosition(this);

 

                this.RectAdder.Visibility = Visibility.Visible;

 

                this.addRect = new Rect(new Point(downPoint.Value.X, downPoint.Value.Y), new Point(pos.X, pos.Y));

 

                Canvas.SetLeft(this.RectAdder, this.addRect.X);

                Canvas.SetTop(this.RectAdder, this.addRect.Y);

                this.RectAdder.Width = Math.Abs(this.addRect.Width);

                this.RectAdder.Height = Math.Abs(this.addRect.Height);

            }

        }

 

        /// <summary>

        /// 마우스를 놓을때 발생되는 이벤트입니다.

        /// </summary>

        /// <param name="e"></param>

        protected override void OnMouseUp(MouseButtonEventArgs e)

        {

            this.ReleaseMouseCapture();

        }

    }

}

 

 

 


EventAggregator 는 이벤트들을 구독하거나 구독기능을 제공하며

컴포넌트 간의 이벤트 처리를 직접적인 참조 없이 가능하게 해줍니다.

 

MainWindow.xaml

<Window x:Class="WpfAppPrism.MainWindow"

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

        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

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

        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

        mc:Ignorable="d"

        Title="MainWindow" Height="450" Width="800">

    <StackPanel VerticalAlignment="Center">

        <Button Name="testButton" Content="Click!" Height="100" Width="200" HorizontalAlignment="Center" Click="testButton_Click"/>

        <Label Content=""/>

        <Button Name="unsubscribeButton" Content="Unsubscribe" Height="100" Width="200" HorizontalAlignment="Center" Click="unsubscribeButton_Click"/>

    </StackPanel>

</Window>

 

 

MainWindow.xaml.cs

using Microsoft.Practices.Composite.Events;

using Microsoft.Practices.Composite.Presentation.Events;

 

using System.Windows;

 

namespace WpfAppPrism

{

    /// <summary>

    /// string 을 인자를 가지는 이벤트 입니다.

    /// CompositePresentationEvent 는 이벤트 게시 및 구독을 관리하는 클래스입니다.

    /// </summary>

    public class SampleStringEvent : CompositePresentationEvent<string> { }

 

    public partial class MainWindow : Window

    {

        /// <summary>

        /// EventAggregator 입니다.

        /// </summary>

        EventAggregator aggregator = new EventAggregator();

 

        /// <summary>

        /// 구독 토큰입니다.

        /// </summary>

        private SubscriptionToken subscriptionToken;

 

        public MainWindow()

        {

            InitializeComponent();

 

            // 이벤트를 구독합니다.

            this.aggregator.GetEvent<SampleStringEvent>().Subscribe(StringAction1);

 

            //이벤트를 구독하고 구독취소를 위해 SubscriptionToken 을 받습니다.

            this.subscriptionToken = this.aggregator.GetEvent<SampleStringEvent>().Subscribe(StringAction2);

        }

 

        /// <summary>

        /// 이벤트 1 입니다.

        /// </summary>

        /// <param name="s"></param>

        private void StringAction1(string s)

        {

            MessageBox.Show($"{s}.StringAction1");

        }

 

        /// <summary>

        /// 이벤트 2 입니다.

        /// </summary>

        /// <param name="s"></param>

        private void StringAction2(string s)

        {

            MessageBox.Show($"{s}.StringAction2");

        }

 

        /// <summary>

        /// test 버튼 클릭 이벤트입니다.

        /// </summary>

        /// <param name="sender"></param>

        /// <param name="e"></param>

        private void testButton_Click(object sender, RoutedEventArgs e)

        {

            // Subscribe 된 이벤트를 실행합니다.

            this.aggregator.GetEvent<SampleStringEvent>().Publish("testButton");

        }

 

        /// <summary>

        /// Unsubscribe 버튼 클릭 이벤트입니다.

        /// </summary>

        /// <param name="sender"></param>

        /// <param name="e"></param>

        private void unsubscribeButton_Click(object sender, RoutedEventArgs e)

        {

            // 이벤트 하나를 Unsubscribe 합니다.

            this.aggregator.GetEvent<SampleStringEvent>().Unsubscribe(this.subscriptionToken);

        }

    }

}

 

 

 

우선 Click! 버튼을 클릭하면 "testButton.StringAction2" 메세지 박스가 나온후 "testButton.StringAction1" 메세지 박스가 나타난다.

구독한 순서 역순으로 이벤트는 발생된다.

 

여기서 Unsubscribe 버튼을 클릭하면 "testButton.StringAction1" 메세지 박스만 나타난다.




 

 

참고 : https://prismlibrary.com/docs/event-aggregator.html


Event Aggregator

Prism library 는 application 상에서 느슨하게 커플링된 component 간의 communication 을 위한 event mechanism 을 제공하는데, event aggrgator 서비스에 기반을 둔 이 mechanism 은 publisher 나 subscriber 가 event를 통해 서로간에 직접적인 참조 없이 communication 이 가능하게 해준다.

EventAggregator 는 동시에 데이터를 주고 받을 수 있는 (multicasting) publish/subscribe 기능을 제공하기도 한다. 이 말은 하나의 event 를 발생시키는 여러개의 publisher 가 있을 수도 있고, 하나의 event 를 수신하는 여러개의 subscriber 들이 있을 수 있다는 의미이다. controller 나 presenter 같은 business logic code 사이에서 message 를 보낼 때 그리고 여러 모듈들 사이에서 event 를 발생시키는 EventAggregator 를 생각해보자.

Prism Library 에서 만들어진 Event 는 typed event 이다. 이 말은 application 이 돌기 전에 compile 시점에서 error 를 감지할 수 있다는 의미이다. 또한 Prisim Library 에서 EventAggregator 는 subscriber 나 publisher 가 특정한 EventBase 를 마음대로 위치시킬 수 있도록 해주기도 하며, 여러개의 publisher / 여러개의 subscriber 를 지원 가능하다.

IEventAggregator

EventAggregator 클래스는 container 에서 service 로서 제공되고, IEventAggregator 인터페이스를 통해 얻을 수 있다. event aggregator 는 event 를 위치시키거나 만들어 내고 event 들의 collection 을 유지하는 역할을 한다.

public interface IEventAggregator { TEventType GetEvent<TEventType>() where TEventType : EventBase; }

EventAggregator 는 현재 생성되어 있지 않다면, 첫 번째 access 시점에 event 를 생성한다. 이로 인해 publisher 나 subscriber 는 해당 event가 사용가능한지 여부를 결정할 필요가 없다.

PubSubEvent

publisher 와 subscriber 를 실제로 연결해주는 것은 PubSubEvent class 인데, Prism library 안의 유일한 EventBase class 의 구현체이다. 이 class 는 subscriber 의 리스트를 유지 관리하고 subscriber 에거 보내는 event 들을 처리한다.

PubSubEvent class 는 generic type으로 정의된, payload type을 인수로 받는 generic class 인데 compile 시점에 event 연결이 잘 될 수 있도록 publisher 와 subscriber 가 적절한 method 를 제공하게 해준다. 아래는 PubSubEvent class 의 일부 정의를 보여준다.

Note

PubSubEvent 는 Prism.Core NuGet package 에 있는 Prism.Events 라는 namespace 에 정의되어 있다.

Creating an Event

PubSubEvent<TPayload> 는 application 또는 module 의 특정 이벤트에 대한 base class 가 되도록 만들어졌다. TPayload 는 event 의 payload 타입인데, payload 라는 것은 event 가 발생했을 때(publish) subscriber 에게 전달되는 argument 이다.

예를 들어, 다음의 코드는 TickerSymbolSelectedEvent 를 보여주고 있는데, payload 는 company symbol를 포함하는 string 값이다. 이 클래스의 구현이 비어 있는 것에 주목하라.

public class TickerSymbolSelectedEvent : PubSubEvent<string>{}

Note

Composite application 상에서 event 들은 주로 여러 module 상에서 공유되는 경우가 많기 때문에 보통은 common 한 곳에 정의한다. "Core" 나 "Infrastructure" 프로젝트 같은 shared assembly 에 정의하는 것이 보통이다.

Publishing an Event

Publisher 는 EventAggregator 로부터 event를 받고 publish method 를 호출함으로써 event 를 발생시킨다. EventAggregtor 에 access 하기 위해서는 class constructor 에 IEventAggregator 타입을 인자로 받도록 추가해서 dependency injection 을 사용할 수 있다.

public class MainPageViewModel { IEventAggregator _eventAggregator; public MainPageViewModel(IEventAggregator ea) { _eventAggregator = ea; } }

다음 코드는 TickerSymbolSelectedEvent 를 publish 하는 예를 보여준다.

_eventAggregator.GetEvent<TickerSymbolSelectedEvent>().Publish("STOCK0");

Subscribing to Events

Subscriber 는 pubSubEvent class 의 overload 가능한 Subscirbe method 중 하나를 이용해서 event 에 참여할 수 있다.

public class MainPageViewModel { public MainPageViewModel(IEventAggregator ea) { ea.GetEvent<TickerSymbolSelectedEvent>().Subscribe(ShowNews); } void ShowNews(string companySymbol) { //implement logic } }

PubSubEvents 를 구독할 수 있는 몇 가지 방법이 있다. 최선의 방법을 결정하는데 다음의 기준들이 도움이 될 것이다.

- event 가 발생했을 때 UI Elements 를 update 하는 경우 : UI thread 에서 해당 event 를 받도록 subscribe 한다.

- event 를 filtering 할 경우 : subscribing 할 때 filter delegate 을 제공한다.

- event 관련하여 performance 고려가 필요한 경우 : subscribing 할 때 strongly referenced delegate를 사용하고 PubSubEvent 에서 manually unsubscribing 한다.

- 위의 그 어떤 것도 적용하기 힘든 경우 : default subscription 을 사용한다.

다음 section에서 이러한 option 들에 대해 알아본다.

Subscribing on the UI Thread

subscriber 는 종종 event에 대한 응답으로 UI Elements를 업데이트할 필요가 있는데, WPF에서는 오직 UI thread 만이 UI elements를 업데이트 할 수가 있다.

기본적으로 subscriber는 publisher 의 thread 상에서 event를 받는다. 만약 publisher 가 UI thread 로부터 event를 보내게 되면 subscriber 는 UI를 업데이트 할 수 있다. 하지만 만약 publisher 의 thread 가 background thread 이면 subscriber 는 직접적으로 UI elements 를 업데이트 할 수가 없다. 이런 경우 subscriber 는 Dispatcher class 를 이용해서 UI thread 상에서 업데이트 되도록 scheduling 을 할 필요가 있다.

Prism Library 에서 제공하는 PubsubEvent 는 subscriber가 UI thread 에서 event 를 자동으로 수신하도록 해줄 수 있는데, subscriber는 이 부분을 다음 코드와 같이 subscription 때 명시해 준다.

public class MainPageViewModel { public MainPageViewModel(IEventAggregator ea) { ea.GetEvent<TickerSymbolSelectedEvent>().Subscribe(ShowNews, ThreadOption.UIThread); } void ShowNews(string companySymbol) { //implement logic } }

ThreadOption 다음과 같다.

- PublisherThread : publisher의 thread 에서 event 를 받도록 하려면 이 option을 사용한다. (default setting)

- BackgroundThread : .NET Framework thread-pool thread 에서 event 를 비동기적으로 받도록 하려면 이 option을 사용한다.

- UITrhead : UI thread 에서 event 를 받도록 하려면 이 option을 사용한다.

Note

PubSubEvent 가 UI thread 상에서 subscriber 에게 event 를 발생시키도록 하기 위해서는 EventAggregator 가 반드시 UI thread 상에서 초기에 생성되어지도록 해야 한다.

Subscription Filtering

subscriber 가 발행된 모든 event들을 하나하나 처리할 필요가 없을 수도 있는데, 이런 상황에서는 subscriber 가 filter parameter 를 사용할 수 있다. filter parameter 는 System.Predicate<TPayLoad> 형식이고, 발생된 event 의 payload 가 subscriber 의 callback method가 실행되어야 하는 조건인지 아닌지 판단해주는 delegate 이기도 하다. 만약 payload 가 조건에 부합하지 않으면 subscriber의 callback 은 실행되지 않게 된다.

이러한 filter 대게 lambda expression으로 구현된다.

public class MainPageViewModel { public MainPageViewModel(IEventAggregator ea) { TickerSymbolSelectedEvent tickerEvent = ea.GetEvent<TickerSymbolSelectedEvent>(); tickerEvent.Subscribe(ShowNews, ThreadOption.UIThread, false, companySymbol => companySymbol == "STOCK0"); } void ShowNews(string companySymbol) { //implement logic } }

Note

Subscribe method 는 추후 해당 event 에 대한 subscription 을 제거하는데 사용되는 Prism.Events.SubscriptionToken 타입의 subscription token 이라는 것을 리턴한다. 이 token 은, callback delegate 으로서 anonymous delegate(익명 대리자) 또는 lambda expression 을 사용했을 때 특별히 유용하게 사용할 수 있다. 또는 다른 filter 로 동일한 event 를 구독하고 있을 때도 마찬가지이다.


+ Recent posts