728x90
반응형
728x170
TestProject.zip
5.66MB
TestProject.z01
10.00MB
TestProject.z02
10.00MB
▶ ImageFileInfo.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Storage;
using Windows.Storage.FileProperties;
using Windows.Storage.Streams;
using Windows.UI.Xaml.Media.Imaging;
namespace TestProject
{
/// <summary>
/// 이미지 파일 정보
/// </summary>
public class ImageFileInfo : INotifyPropertyChanged
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Event
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 속성 변경시 이벤트 - PropertyChanged
/// <summary>
/// 속성 변경시 이벤트
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 노출
/// </summary>
private float exposure = 0;
/// <summary>
/// 온도
/// </summary>
private float temperature = 0;
/// <summary>
/// 색조
/// </summary>
private float tint = 0;
/// <summary>
/// 대비
/// </summary>
private float contrast = 0;
/// <summary>
/// 채도
/// </summary>
private float saturation = 1;
/// <summary>
/// 불선명도
/// </summary>
private float blur = 0;
/// <summary>
/// 저장 필요 여부
/// </summary>
private bool needsSaved = false;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 이미지 속성 - ImageProperties
/// <summary>
/// 이미지 속성
/// </summary>
public ImageProperties ImageProperties { get; }
#endregion
#region 이미지 파일 - ImageFile
/// <summary>
/// 이미지 파일
/// </summary>
public StorageFile ImageFile { get; }
#endregion
#region 이미지명 - ImageName
/// <summary>
/// 이미지명
/// </summary>
public string ImageName { get; }
#endregion
#region 이미지 파일 타입 - ImageFileType
/// <summary>
/// 이미지 파일 타입
/// </summary>
public string ImageFileType { get; }
#endregion
#region 이미지 제목 - ImageTitle
/// <summary>
/// 이미지 제목
/// </summary>
public string ImageTitle
{
get => string.IsNullOrEmpty(ImageProperties.Title) ? ImageName : ImageProperties.Title;
set
{
if(ImageProperties.Title != value)
{
ImageProperties.Title = value;
IAsyncAction action = ImageProperties.SavePropertiesAsync();
FirePropertyChangedEvent();
}
}
}
#endregion
#region 이미지 등급 - ImageRating
/// <summary>
/// 이미지 등급
/// </summary>
public int ImageRating
{
get => (int)ImageProperties.Rating;
set
{
if(ImageProperties.Rating != value)
{
ImageProperties.Rating = (uint)value;
IAsyncAction action = ImageProperties.SavePropertiesAsync();
FirePropertyChangedEvent();
}
}
}
#endregion
#region 이미지 크기 - ImageDimensions
/// <summary>
/// 이미지 크기
/// </summary>
public string ImageDimensions => $"{ImageProperties.Width} x {ImageProperties.Height}";
#endregion
#region 노출 - Exposure
/// <summary>
/// 노출
/// </summary>
public float Exposure
{
get => this.exposure;
set => SetEditingProperty(ref this.exposure, value);
}
#endregion
#region 온도 - Temperature
/// <summary>
/// 온도
/// </summary>
public float Temperature
{
get => this.temperature;
set => SetEditingProperty(ref this.temperature, value);
}
#endregion
#region 색조 - Tint
/// <summary>
/// 색조
/// </summary>
public float Tint
{
get => this.tint;
set => SetEditingProperty(ref this.tint, value);
}
#endregion
#region 대비 - Contrast
/// <summary>
/// 대비
/// </summary>
public float Contrast
{
get => this.contrast;
set => SetEditingProperty(ref this.contrast, value);
}
#endregion
#region 채도 - Saturation
/// <summary>
/// 채도
/// </summary>
public float Saturation
{
get => this.saturation;
set => SetEditingProperty(ref this.saturation, value);
}
#endregion
#region 불선명도 - Blur
/// <summary>
/// 불선명도
/// </summary>
public float Blur
{
get => this.blur;
set => SetEditingProperty(ref this.blur, value);
}
#endregion
#region 저장 필요 여부 - NeedsSaved
/// <summary>
/// 저장 필요 여부
/// </summary>
public bool NeedsSaved
{
get => this.needsSaved;
set => SetProperty(ref this.needsSaved, value);
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - ImageFileInfo(imageProperties, imageFile, imageName, imageFileType)
/// <summary>
/// 생성자
/// </summary>
/// <param name="imageProperties">이미지 속성</param>
/// <param name="imageFile">이미지 파일</param>
/// <param name="imageName">이미지명</param>
/// <param name="imageFileType">이미지 파일 타입</param>
public ImageFileInfo(ImageProperties imageProperties, StorageFile imageFile, string imageName, string imageFileType)
{
ImageProperties = imageProperties;
ImageFile = imageFile;
ImageName = imageName;
ImageFileType = imageFileType;
Random random = new Random();
int rating = (int)imageProperties.Rating;
ImageRating = rating == 0 ? random.Next(1, 5) : rating;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 비트맵 이미지 구하기 (비동기) - GetBitmapImageAsyc()
/// <summary>
/// 비트맵 이미지 구하기 (비동기)
/// </summary>
/// <returns>비트맵 이미지 태스크</returns>
public async Task<BitmapImage> GetBitmapImageAsyc()
{
using(IRandomAccessStream stream = await ImageFile.OpenReadAsync())
{
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.SetSource(stream);
return bitmapImage;
}
}
#endregion
#region 썸네일 비트맵 이미지 구하기 (비동기) - GetThumbnailBitmapImageAsync()
/// <summary>
/// 썸네일 비트맵 이미지 구하기 (비동기)
/// </summary>
/// <returns>썸네일 비트맵 이미지</returns>
public async Task<BitmapImage> GetThumbnailBitmapImageAsync()
{
StorageItemThumbnail thumbnail = await ImageFile.GetThumbnailAsync(ThumbnailMode.PicturesView);
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.SetSource(thumbnail);
thumbnail.Dispose();
return bitmapImage;
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region 속성 변경시 이벤트 발생시키기 - FirePropertyChangedEvent(propertyName)
/// <summary>
/// 속성 변경시 이벤트 발생시키기
/// </summary>
/// <param name="propertyName">속성명</param>
protected void FirePropertyChangedEvent([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
#endregion
#region 편집 속성 설정하기 - SetEditingProperty<T>(storage, value, propertyName)
/// <summary>
/// 편집 속성 설정하기
/// </summary>
/// <typeparam name="T">타입</typeparam>
/// <param name="storage">저장소</param>
/// <param name="value">값</param>
/// <param name="propertyName">속성명</param>
/// <returns>처리 결과</returns>
protected bool SetEditingProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if(SetProperty(ref storage, value, propertyName))
{
if(Exposure != 0 || Temperature != 0 || Tint != 0 || Contrast != 0 || Saturation != 1 || Blur != 0)
{
NeedsSaved = true;
}
else
{
NeedsSaved = false;
}
return true;
}
else
{
return false;
}
}
#endregion
#region 속성 설정하기 - SetProperty<T>(storage, value, propertyName)
/// <summary>
/// 속성 설정하기
/// </summary>
/// <typeparam name="T">타입</typeparam>
/// <param name="storage">저장소</param>
/// <param name="value">값</param>
/// <param name="propertyName">속성명</param>
/// <returns>처리 결과</returns>
protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if(object.Equals(storage, value))
{
return false;
}
else
{
storage = value;
FirePropertyChangedEvent(propertyName);
return true;
}
}
#endregion
}
}
728x90
▶ CustomCompositionBrush.cs
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using System;
using Windows.UI.Composition;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media;
using Windows.Storage.Streams;
namespace TestProject
{
/// <summary>
/// 커스텀 혼합 브러시
/// </summary>
public class CustomCompositionBrush : XamlCompositionBrushBase
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region 불선명도 양 속성 - BlurAmountProperty
/// <summary>
/// 불선명도 양 속성
/// </summary>
public static readonly DependencyProperty BlurAmountProperty = DependencyProperty.Register
(
"BlurAmount",
typeof(double),
typeof(CustomCompositionBrush),
new PropertyMetadata
(
0.0,
BlurAmountPropertyChangedCallback
)
);
#endregion
#region 대비 양 속성 - ContrastAmountProperty
/// <summary>
/// 대비 양 속성
/// </summary>
public static readonly DependencyProperty ContrastAmountProperty = DependencyProperty.Register
(
"ContrastAmount",
typeof(double),
typeof(CustomCompositionBrush),
new PropertyMetadata
(
0.0,
new PropertyChangedCallback(ContrastAmountPropertyChangedCallback)
)
);
#endregion
#region 채도 양 속성 - SaturationAmountProperty
/// <summary>
/// 채도 양 속성
/// </summary>
public static readonly DependencyProperty SaturationAmountProperty = DependencyProperty.Register
(
"SaturationAmount",
typeof(double),
typeof(CustomCompositionBrush),
new PropertyMetadata
(
1.0,
new PropertyChangedCallback(SaturationAmountPropertyChangedCallback)
)
);
#endregion
#region 노출 양 속성 - ExposureAmountProperty
/// <summary>
/// 노출 양 속성
/// </summary>
public static readonly DependencyProperty ExposureAmountProperty = DependencyProperty.Register
(
"ExposureAmount",
typeof(double),
typeof(CustomCompositionBrush),
new PropertyMetadata
(
0.0,
new PropertyChangedCallback(ExposureAmountPropertyChangedCallback)
)
);
#endregion
#region 색조 양 속성 - TintAmountProperty
/// <summary>
/// 색조 양 속성
/// </summary>
public static readonly DependencyProperty TintAmountProperty = DependencyProperty.Register
(
"TintAmount",
typeof(double),
typeof(CustomCompositionBrush),
new PropertyMetadata
(
0.0,
new PropertyChangedCallback(TintAmountPropertyChangedCallback)
)
);
#endregion
#region 온도 양 속성 - TemperatureAmountProperty
/// <summary>
/// 온도 양 속성
/// </summary>
public static readonly DependencyProperty TemperatureAmountProperty = DependencyProperty.Register
(
"TemperatureAmount",
typeof(double),
typeof(CustomCompositionBrush),
new PropertyMetadata
(
0.0,
new PropertyChangedCallback(TemperatureAmountPropertyChangedCallback)
)
);
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 이미지 로딩 여부
/// </summary>
private bool isImageLoading = false;
/// <summary>
/// 로드된 이미지 표면
/// </summary>
private LoadedImageSurface loadedImageSurface;
/// <summary>
/// 혼합 효과 브러시
/// </summary>
private CompositionEffectBrush compositionEffectBrush;
/// <summary>
/// 대비 효과
/// </summary>
private ContrastEffect contrastEffect;
/// <summary>
/// 노출 효과
/// </summary>
private ExposureEffect exposureEffect;
/// <summary>
/// 온도/색조 효과
/// </summary>
private TemperatureAndTintEffect temperatureAndTintEffect;
/// <summary>
/// 가우스안 불선명 효과
/// </summary>
private GaussianBlurEffect gaussianBlurEffect;
/// <summary>
/// 채도 효과
/// </summary>
private SaturationEffect saturationEffect;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 이미지 - Image
/// <summary>
/// 이미지
/// </summary>
public ICanvasImage Image
{
get
{
this.contrastEffect.Contrast = (float)ContrastAmount;
this.exposureEffect.Exposure = (float)ExposureAmount;
this.temperatureAndTintEffect.Tint = (float)TintAmount;
this.temperatureAndTintEffect.Temperature = (float)TemperatureAmount;
this.saturationEffect.Saturation = (float)SaturationAmount;
this.gaussianBlurEffect.BlurAmount = (float)BlurAmount;
return this.gaussianBlurEffect;
}
}
#endregion
#region 불선명도 양 - BlurAmount
/// <summary>
/// 불선명도 양
/// </summary>
public double BlurAmount
{
get
{
return (double)GetValue(BlurAmountProperty);
}
set
{
SetValue(BlurAmountProperty, value);
}
}
#endregion
#region 대비 양 - ContrastAmount
/// <summary>
/// 대비 양
/// </summary>
public double ContrastAmount
{
get
{
return (double)GetValue(ContrastAmountProperty);
}
set
{
SetValue(ContrastAmountProperty, value);
}
}
#endregion
#region 채도 양 - SaturationAmount
/// <summary>
/// 채도 양
/// </summary>
public double SaturationAmount
{
get
{
return (double)GetValue(SaturationAmountProperty);
}
set
{
SetValue(SaturationAmountProperty, value);
}
}
#endregion
#region 노출 양 - ExposureAmount
/// <summary>
/// 노출 양
/// </summary>
public double ExposureAmount
{
get
{
return (double)GetValue(ExposureAmountProperty);
}
set
{
SetValue(ExposureAmountProperty, value);
}
}
#endregion
#region 색조 양 - TintAmount
/// <summary>
/// 색조 양
/// </summary>
public double TintAmount
{
get
{
return (double)GetValue(TintAmountProperty);
}
set
{
SetValue(TintAmountProperty, value);
}
}
#endregion
#region 온도 양 - TemperatureAmount
/// <summary>
/// 온도 양
/// </summary>
public double TemperatureAmount
{
get
{
return (double)GetValue(TemperatureAmountProperty);
}
set
{
SetValue(TemperatureAmountProperty, value);
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - CustomCompositionBrush()
/// <summary>
/// 생성자
/// </summary>
public CustomCompositionBrush()
{
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Private
#region 불선명도 양 속성 변경시 콜백 처리하기 - BlurAmountPropertyChangedCallback(d, e)
/// <summary>
/// 불선명도 양 속성 변경시 콜백 처리하기
/// </summary>
/// <param name="d">의존 객체</param>
/// <param name="e">이벤트 인자</param>
private static void BlurAmountPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomCompositionBrush brush = d as CustomCompositionBrush;
brush.CompositionBrush?.Properties.InsertScalar("Blur.BlurAmount", (float)(double)e.NewValue);
}
#endregion
#region 대비 양 속성 변경시 콜백 처리하기 - ContrastAmountPropertyChangedCallback(d, e)
/// <summary>
/// 대비 양 속성 변경시 콜백 처리하기
/// </summary>
/// <param name="d">의존 객체</param>
/// <param name="e">이벤트 인자</param>
private static void ContrastAmountPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomCompositionBrush brush = d as CustomCompositionBrush;
brush.CompositionBrush?.Properties.InsertScalar("ContrastEffect.Contrast", (float)(double)e.NewValue);
}
#endregion
#region 채도 양 속성 변경시 콜백 처리하기 - SaturationAmountPropertyChangedCallback(d, e)
/// <summary>
/// 채도 양 속성 변경시 콜백 처리하기
/// </summary>
/// <param name="d">의존 객체</param>
/// <param name="e">이벤트 인자</param>
private static void SaturationAmountPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomCompositionBrush brush = d as CustomCompositionBrush;
brush.CompositionBrush?.Properties.InsertScalar("SaturationEffect.Saturation", (float)(double)e.NewValue);
}
#endregion
#region 노출 양 속성 변경시 콜백 처리하기 - ExposureAmountPropertyChangedCallback(d, e)
/// <summary>
/// 노출 양 속성 변경시 콜백 처리하기
/// </summary>
/// <param name="d">의존 객체</param>
/// <param name="e">이벤트 인자</param>
private static void ExposureAmountPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomCompositionBrush brush = d as CustomCompositionBrush;
brush.CompositionBrush?.Properties.InsertScalar("ExposureEffect.Exposure", (float)(double)e.NewValue);
}
#endregion
#region 색조 양 속성 변경시 콜백 처리하기 - TintAmountPropertyChangedCallback(d, e)
/// <summary>
/// 색조 양 속성 변경시 콜백 처리하기
/// </summary>
/// <param name="d">의존 객체</param>
/// <param name="e">이벤트 인자</param>
private static void TintAmountPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomCompositionBrush brush = d as CustomCompositionBrush;
brush.CompositionBrush?.Properties.InsertScalar("TemperatureAndTintEffect.Tint", (float)(double)e.NewValue);
}
#endregion
#region 온도 양 속성 변경시 콜백 처리하기 - TemperatureAmountPropertyChangedCallback(d, e)
/// <summary>
/// 온도 양 속성 변경시 콜백 처리하기
/// </summary>
/// <param name="d">의존 객체</param>
/// <param name="e">이벤트 인자</param>
private static void TemperatureAmountPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CustomCompositionBrush brush = d as CustomCompositionBrush;
brush.CompositionBrush?.Properties.InsertScalar("TemperatureAndTintEffect.Temperature", (float)(double)e.NewValue);
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Instance
//////////////////////////////////////////////////////////////////////////////// Public
#region 소스 설정하기 - SetSource(source)
/// <summary>
/// 소스 설정하기
/// </summary>
/// <param name="source">소스</param>
public void SetSource(ICanvasImage source)
{
this.saturationEffect.Source = source;
}
#endregion
#region 이미지 로드하기 - LoadImage(filePath)
/// <summary>
/// 이미지 로드하기
/// </summary>
/// <param name="filePath">파일 경로</param>
public void LoadImage(string filePath)
{
Compositor compositor = Window.Current.Compositor;
this.loadedImageSurface = LoadedImageSurface.StartLoadFromUri(new Uri(filePath));
this.loadedImageSurface.LoadCompleted += loadedImageSurface_LoadedCompleted;
}
#endregion
#region 이미지 로드하기 - LoadImage(stream)
/// <summary>
/// 이미지 로드하기
/// </summary>
/// <param name="stream">스트림</param>
public void LoadImage(IRandomAccessStream stream)
{
if(stream != null && this.isImageLoading == false)
{
Compositor compositor = Window.Current.Compositor;
this.isImageLoading = true;
this.loadedImageSurface = LoadedImageSurface.StartLoadFromStream(stream);
this.loadedImageSurface.LoadCompleted += loadedImageSurface_LoadedCompleted;
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Protected
#region 연결 취소시 처리하기 - OnDisconnected()
/// <summary>
/// 연결 취소시 처리하기
/// </summary>
protected override void OnDisconnected()
{
if(this.loadedImageSurface != null)
{
this.loadedImageSurface.Dispose();
this.loadedImageSurface = null;
}
if(CompositionBrush != null)
{
CompositionBrush.Dispose();
CompositionBrush = null;
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Private
#region 로드된 이미지 표면 로드 완료시 처리하기 - loadedImageSurface_LoadedCompleted(sender, e)
/// <summary>
/// 로드된 이미지 표면 로드 완료시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void loadedImageSurface_LoadedCompleted(LoadedImageSurface sender, LoadedImageSourceLoadCompletedEventArgs e)
{
this.isImageLoading = false;
if(e.Status == LoadedImageSourceLoadStatus.Success)
{
Compositor compositor = Window.Current.Compositor;
CompositionSurfaceBrush brush = compositor.CreateSurfaceBrush(this.loadedImageSurface);
brush.Stretch = CompositionStretch.UniformToFill;
this.saturationEffect = new SaturationEffect()
{
Name = "SaturationEffect",
Saturation = (float)SaturationAmount,
Source = new CompositionEffectSourceParameter("image")
};
this.contrastEffect = new ContrastEffect()
{
Name = "ContrastEffect",
Contrast = (float)ContrastAmount,
Source = this.saturationEffect
};
this.exposureEffect = new ExposureEffect()
{
Name = "ExposureEffect",
Source = this.contrastEffect,
Exposure = (float)ExposureAmount,
};
this.temperatureAndTintEffect = new TemperatureAndTintEffect()
{
Name = "TemperatureAndTintEffect",
Source = this.exposureEffect,
Temperature = (float)TemperatureAmount,
Tint = (float)TintAmount
};
this.gaussianBlurEffect = new GaussianBlurEffect()
{
Name = "Blur",
Source = this.temperatureAndTintEffect,
BlurAmount = (float)BlurAmount,
BorderMode = EffectBorderMode.Hard,
};
CompositionEffectFactory factory = compositor.CreateEffectFactory
(
this.gaussianBlurEffect,
new[]
{
"SaturationEffect.Saturation",
"ExposureEffect.Exposure",
"Blur.BlurAmount",
"TemperatureAndTintEffect.Temperature",
"TemperatureAndTintEffect.Tint",
"ContrastEffect.Contrast"
}
);
this.compositionEffectBrush = factory.CreateBrush();
this.compositionEffectBrush.SetSourceParameter("image", brush);
CompositionBrush = this.compositionEffectBrush;
}
else
{
LoadImage("ms-appx:///Assets/StoreLogo.png");
}
}
#endregion
}
}
300x250
▶ DetailPage.xaml
<Page x:Class="TestProject.DetailPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:local="using:TestProject"
RequestedTheme="Dark">
<Page.Resources>
<Flyout x:Key="zoomFlyoutKey">
<StackPanel>
<StackPanel.Resources>
<Style TargetType="Button">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0 2" />
</Style>
</StackPanel.Resources>
<Slider Name="zoomSlider"
Margin="0 5 0 0"
StepFrequency="0.1"
Width="100"
Minimum="0.1"
Maximum="5"
Value="1"
ValueChanged="zoomSlider_ValueChanged" />
<Button
Content="화면 맞춤"
Click="{x:Bind FitToScreen}" />
<Button
Content="실제 크기 표시"
Click="{x:Bind ShowActualSize}" />
</StackPanel>
</Flyout>
<ControlTemplate x:Key="FancySliderControlTemplateKey" TargetType="Slider"
xmlns:contract6Present="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract,6)"
xmlns:contract6NotPresent="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractNotPresent(Windows.Foundation.UniversalApiContract,6)"
xmlns:contract7Present="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract,7)"
xmlns:contract7NotPresent="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractNotPresent(Windows.Foundation.UniversalApiContract,7)">
<Grid Margin="{TemplateBinding Padding}">
<Grid.Resources>
<Style x:Key="SliderThumbStyleKey" TargetType="Thumb">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="{ThemeResource SliderThumbBackground}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Thumb">
<Border
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{ThemeResource SliderThumbCornerRadius}"
Background="{TemplateBinding Background}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="HorizontalTrackRect"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="VerticalTrackRect"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="HorizontalThumb"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="VerticalThumb"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="SliderContainer"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="HorizontalDecreaseRect"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPressed}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="VerticalDecreaseRect"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPressed}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="headerContentPresenter"
Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderHeaderForegroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="HorizontalDecreaseRect"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="HorizontalTrackRect"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="VerticalDecreaseRect"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="VerticalTrackRect"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="HorizontalThumb"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="VerticalThumb"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="TopTickBar"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="BottomTickBar"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="LeftTickBar"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="RightTickBar"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTickBarFillDisabled}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="SliderContainer"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundDisabled}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="HorizontalTrackRect"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="VerticalTrackRect"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="HorizontalThumb"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="VerticalThumb"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderThumbBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="SliderContainer"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderContainerBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="HorizontalDecreaseRect"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="VerticalDecreaseRect"
Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SliderTrackValueFillPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusEngagementStates">
<VisualState x:Name="FocusDisengaged" />
<VisualState x:Name="FocusEngagedHorizontal">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="SliderContainer"
Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
<DiscreteObjectKeyFrame KeyTime="0" Value="False" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="HorizontalThumb"
Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
<DiscreteObjectKeyFrame KeyTime="0" Value="True" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="FocusEngagedVertical">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="SliderContainer"
Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
<DiscreteObjectKeyFrame KeyTime="0" Value="False" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetName="VerticalThumb"
Storyboard.TargetProperty="(Control.IsTemplateFocusTarget)">
<DiscreteObjectKeyFrame KeyTime="0" Value="True" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ContentPresenter Name="headerContentPresenter" Grid.Row="0"
ContentTemplate="{TemplateBinding HeaderTemplate}"
Margin="{ThemeResource SliderTopHeaderMargin}"
x:DeferLoadStrategy="Lazy"
Foreground="{ThemeResource SliderHeaderForeground}"
FontWeight="{ThemeResource SliderHeaderThemeFontWeight}"
TextWrapping="Wrap"
Visibility="Collapsed"
Content="{TemplateBinding Header}" />
<Grid Name="SliderContainer" Grid.Row="1"
Background="{ThemeResource SliderContainerBackground}"
Control.IsTemplateFocusTarget="True">
<Grid Name="HorizontalTemplate"
MinHeight="{ThemeResource SliderHorizontalHeight}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="{ThemeResource SliderPreContentMargin}" />
<RowDefinition Height="Auto" />
<RowDefinition Height="{ThemeResource SliderPostContentMargin}" />
</Grid.RowDefinitions>
<Polygon Grid.Row="0" Grid.RowSpan="3" Grid.ColumnSpan="3"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Height="20"
Fill="{TemplateBinding Background}"
Stretch="Fill"
Points="0 20 200 20 200 0" />
<Rectangle Name="HorizontalTrackRect" Grid.Row="1" Grid.ColumnSpan="3"
Height="{ThemeResource SliderTrackThemeHeight}"
contract7Present:RadiusX="{Binding CornerRadius,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource TopLeftCornerRadiusDoubleValueConverter}}"
contract7Present:RadiusY="{Binding CornerRadius,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource BottomRightCornerRadiusDoubleValueConverter}}"
contract7NotPresent:RadiusX="{Binding Source={ThemeResource ControlCornerRadius},
Converter={StaticResource TopLeftCornerRadiusDoubleValueConverter}}"
contract7NotPresent:RadiusY="{Binding Source={ThemeResource ControlCornerRadius},
Converter={StaticResource BottomRightCornerRadiusDoubleValueConverter}}" />
<Rectangle Name="HorizontalDecreaseRect" Grid.Row="1"
contract7Present:RadiusX="{Binding CornerRadius,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource TopLeftCornerRadiusDoubleValueConverter}}"
contract7Present:RadiusY="{Binding CornerRadius,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource BottomRightCornerRadiusDoubleValueConverter}}"
contract7NotPresent:RadiusX="{Binding Source={ThemeResource ControlCornerRadius},
Converter={StaticResource TopLeftCornerRadiusDoubleValueConverter}}"
contract7NotPresent:RadiusY="{Binding Source={ThemeResource ControlCornerRadius},
Converter={StaticResource BottomRightCornerRadiusDoubleValueConverter}}"
Fill="{TemplateBinding Foreground}" />
<TickBar Name="TopTickBar" Grid.ColumnSpan="3"
Margin="0 0 0 4"
VerticalAlignment="Bottom"
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
Fill="{ThemeResource SliderTickBarFill}"
Visibility="Collapsed" />
<TickBar Name="HorizontalInlineTickBar" Grid.Row="1" Grid.ColumnSpan="3"
Height="{ThemeResource SliderTrackThemeHeight}"
Fill="{ThemeResource SliderInlineTickBarFill}"
Visibility="Collapsed" />
<TickBar Name="BottomTickBar" Grid.Row="2" Grid.ColumnSpan="3"
Margin="0 4 0 0"
VerticalAlignment="Top"
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
Fill="{ThemeResource SliderTickBarFill}"
Visibility="Collapsed" />
<Thumb Name="HorizontalThumb" Grid.Row="0" Grid.RowSpan="3" Grid.Column="1"
Style="{StaticResource SliderThumbStyleKey}"
Width="{ThemeResource SliderHorizontalThumbWidth}"
Height="{ThemeResource SliderHorizontalThumbHeight}"
FocusVisualMargin="-14 -6 -14 -6"
AutomationProperties.AccessibilityView="Raw"
DataContext="{TemplateBinding Value}" />
</Grid>
<Grid Name="VerticalTemplate"
MinWidth="{ThemeResource SliderVerticalWidth}"
Visibility="Collapsed">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{ThemeResource SliderPreContentMargin}" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="{ThemeResource SliderPostContentMargin}" />
</Grid.ColumnDefinitions>
<Rectangle Name="VerticalTrackRect" Grid.RowSpan="3" Grid.Column="1"
Width="{ThemeResource SliderTrackThemeHeight}"
contract7Present:RadiusX="{Binding CornerRadius,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource TopLeftCornerRadiusDoubleValueConverter}}"
contract7Present:RadiusY="{Binding CornerRadius,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource BottomRightCornerRadiusDoubleValueConverter}}"
contract7NotPresent:RadiusX="{Binding Source={ThemeResource ControlCornerRadius},
Converter={StaticResource TopLeftCornerRadiusDoubleValueConverter}}"
contract7NotPresent:RadiusY="{Binding Source={ThemeResource ControlCornerRadius},
Converter={StaticResource BottomRightCornerRadiusDoubleValueConverter}}"
Fill="{TemplateBinding Background}" />
<Rectangle Name="VerticalDecreaseRect" Grid.Row="2" Grid.Column="1"
contract7Present:RadiusX="{Binding CornerRadius,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource TopLeftCornerRadiusDoubleValueConverter}}"
contract7Present:RadiusY="{Binding CornerRadius,
RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource BottomRightCornerRadiusDoubleValueConverter}}"
contract7NotPresent:RadiusX="{Binding Source={ThemeResource ControlCornerRadius},
Converter={StaticResource TopLeftCornerRadiusDoubleValueConverter}}"
contract7NotPresent:RadiusY="{Binding Source={ThemeResource ControlCornerRadius},
Converter={StaticResource BottomRightCornerRadiusDoubleValueConverter}}"
Fill="{TemplateBinding Foreground}" />
<TickBar Name="LeftTickBar" Grid.RowSpan="3"
Margin="0 0 4 0"
HorizontalAlignment="Right"
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
Fill="{ThemeResource SliderTickBarFill}"
Visibility="Collapsed" />
<TickBar Name="VerticalInlineTickBar" Grid.RowSpan="3" Grid.Column="1"
Width="{ThemeResource SliderTrackThemeHeight}"
Fill="{ThemeResource SliderInlineTickBarFill}"
Visibility="Collapsed" />
<TickBar Name="RightTickBar" Grid.RowSpan="3" Grid.Column="2"
Margin="4 0 0 0"
HorizontalAlignment="Left"
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
Fill="{ThemeResource SliderTickBarFill}"
Visibility="Collapsed" />
<Thumb Name="VerticalThumb" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3"
Style="{StaticResource SliderThumbStyleKey}"
Width="{ThemeResource SliderVerticalThumbWidth}"
Height="{ThemeResource SliderVerticalThumbHeight}"
FocusVisualMargin="-6 -14 -6 -14"
AutomationProperties.AccessibilityView="Raw"
DataContext="{TemplateBinding Value}" />
</Grid>
</Grid>
</Grid>
</ControlTemplate>
</Page.Resources>
<RelativePanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel Name="titlePanel" Orientation="Horizontal">
<TextBlock Name="titleTextBlock"
Style="{StaticResource TitleTextBlockStyle}"
Margin="24 0 0 24"
Text="{x:Bind imageFileInfo.ImageTitle, Mode=OneWay}" />
<TextBlock
Style="{StaticResource TitleTextBlockStyle}"
Visibility="{x:Bind imageFileInfo.NeedsSaved, Mode=OneWay}"
Text="*" />
</StackPanel>
<CommandBar Name="mainCommandBar"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignTopWithPanel="True"
OverflowButtonVisibility="Collapsed"
DefaultLabelPosition="Right">
<AppBarButton Name="zoomButton"
Icon="Zoom"
Label="Zoom"
Flyout="{StaticResource zoomFlyoutKey}" />
<AppBarToggleButton Name="editButton"
Icon="Edit"
Label="Edit"
Click="{x:Bind ToggleEditState}" />
</CommandBar>
<SplitView Name="mainSplitView"
DisplayMode="Inline"
PanePlacement="Right"
RelativePanel.Below="mainCommandBar"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignBottomWithPanel="True">
<Grid>
<Image Name="targetImage"
Stretch="None" />
<ScrollViewer Name="imageScrollVierwer"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
ZoomMode="Enabled"
HorizontalScrollMode="Auto"
HorizontalScrollBarVisibility="Auto"
ViewChanged="imageScrollViewer_ViewChanged">
<Rectangle Name="imageRectangle"
Width="{x:Bind imageFileInfo.ImageProperties.Width, Mode=OneWay}"
Height="{x:Bind imageFileInfo.ImageProperties.Height, Mode=OneWay}"
x:Load="True">
<Rectangle.Fill>
<local:CustomCompositionBrush x:Name="customCompositionBrush"
BlurAmount="{x:Bind imageFileInfo.Blur, Mode=OneWay}"
SaturationAmount="{x:Bind imageFileInfo.Saturation, Mode=OneWay}"
ContrastAmount="{x:Bind imageFileInfo.Contrast, Mode=OneWay}"
ExposureAmount="{x:Bind imageFileInfo.Exposure, Mode=OneWay}"
TintAmount="{x:Bind imageFileInfo.Tint, Mode=OneWay}"
TemperatureAmount="{x:Bind imageFileInfo.Temperature, Mode=OneWay}" />
</Rectangle.Fill>
</Rectangle>
</ScrollViewer>
</Grid>
<SplitView.Pane>
<ScrollViewer>
<Grid Name="editControlGrid"
Margin="24 48 24 24"
HorizontalAlignment="Stretch"
x:Load="{x:Bind mainSplitView.IsPaneOpen, Mode=OneWay}">
<Grid.Resources>
<x:Double x:Key="SliderTrackThemeHeight">4</x:Double>
<Style TargetType="Slider" BasedOn="{StaticResource DefaultSliderStyle}">
<Setter Property="Margin" Value="0 0 0 0" />
<Setter Property="Padding" Value="0" />
<Setter Property="MinWidth" Value="100" />
<Setter Property="StepFrequency" Value="0.1" />
<Setter Property="TickFrequency" Value="0.1" />
<Setter Property="Grid.ColumnSpan" Value="2" />
</Style>
<Style x:Key="ValueTextBlockStyleKey" TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Right" />
<Setter Property="Grid.Column" Value="1" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="144" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Name="titleEditBox" Grid.ColumnSpan="2"
Height="80"
Padding="12"
Header="제목"
Text="{x:Bind imageFileInfo.ImageTitle, Mode=TwoWay}" />
<muxc:RatingControl Grid.Row="1" Grid.ColumnSpan="2"
Margin="0 0 0 16"
Padding="12"
Value="{x:Bind imageFileInfo.ImageRating, Mode=TwoWay}" />
<Slider Header="노출" Grid.Row="2"
Template="{StaticResource FancySliderControlTemplateKey}"
Foreground="Transparent"
Minimum="-2"
Maximum="2"
Value="{x:Bind imageFileInfo.Exposure, Mode=TwoWay}">
<Slider.Background>
<LinearGradientBrush
StartPoint="0 0.5"
EndPoint="1 0.5">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Black" />
<GradientStop Offset="1" Color="White" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Slider.Background>
</Slider>
<TextBlock Grid.Row="2"
Style="{StaticResource ValueTextBlockStyleKey}"
Text="{x:Bind imageFileInfo.Exposure.ToString('N', cultureInfo), Mode=OneWay}" />
<Slider Header="온도" Grid.Row="3"
Template="{StaticResource FancySliderControlTemplateKey}"
Foreground="Transparent"
Minimum="-1"
Maximum="1"
Value="{x:Bind imageFileInfo.Temperature, Mode=TwoWay}">
<Slider.Background>
<LinearGradientBrush
StartPoint="0 0.5"
EndPoint="1 0.5">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Blue" />
<GradientStop Offset="1" Color="Yellow" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Slider.Background>
</Slider>
<TextBlock Grid.Row="3"
Style="{StaticResource ValueTextBlockStyleKey}"
Text="{x:Bind imageFileInfo.Temperature.ToString('N', cultureInfo), Mode=OneWay}" />
<Slider Header="색조" Grid.Row="4"
Template="{StaticResource FancySliderControlTemplateKey}"
Foreground="Transparent"
Minimum="-1"
Maximum="1"
Value="{x:Bind imageFileInfo.Tint, Mode=TwoWay}">
<Slider.Background>
<LinearGradientBrush
StartPoint="0 0.5"
EndPoint="1 0.5">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Red" />
<GradientStop Offset="1" Color="Green" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Slider.Background>
</Slider>
<TextBlock Grid.Row="4"
Style="{StaticResource ValueTextBlockStyleKey}"
Text="{x:Bind imageFileInfo.Tint.ToString('N', cultureInfo), Mode=OneWay}" />
<Slider Header="대비" Grid.Row="5"
Foreground="Transparent"
Minimum="-1"
Maximum="1"
Value="{x:Bind imageFileInfo.Contrast, Mode=TwoWay}" />
<TextBlock Grid.Row="5"
Style="{StaticResource ValueTextBlockStyleKey}"
Text="{x:Bind imageFileInfo.Contrast.ToString('N', cultureInfo), Mode=OneWay}" />
<Slider Header="채도" Grid.Row="6"
Minimum="0"
Maximum="1"
Value="{x:Bind imageFileInfo.Saturation, Mode=TwoWay}" />
<TextBlock Grid.Row="6"
Style="{StaticResource ValueTextBlockStyleKey}"
Text="{x:Bind imageFileInfo.Saturation.ToString('N', cultureInfo), Mode=OneWay}" />
<Slider Header="불선명도" Grid.Row="7"
Minimum="0"
Maximum="5"
Value="{x:Bind imageFileInfo.Blur, Mode=TwoWay}" />
<TextBlock Grid.Row="7"
Style="{StaticResource ValueTextBlockStyleKey}"
Text="{x:Bind imageFileInfo.Blur.ToString('N', cultureInfo), Mode=OneWay}" />
<Image Name="smallImage" Grid.Row="8" Grid.ColumnSpan="2"
Width="300"
Height="200"
Loaded="SmallImage_Loaded" />
<Grid Grid.Row="9" Grid.ColumnSpan="2"
VerticalAlignment="Bottom">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button Content="초기화"
Margin="0 0 2 0"
HorizontalAlignment="Stretch"
Click="{x:Bind ResetEffects}" />
<Button Content="저장..." Grid.Column="1"
Margin="2 0 0 0"
HorizontalAlignment="Stretch"
Click="{x:Bind ExportImage}" />
</Grid>
</Grid>
</ScrollViewer>
</SplitView.Pane>
</SplitView>
</RelativePanel>
</Page>
▶ DetailPage.xaml.cs
using Microsoft.Graphics.Canvas;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Threading.Tasks;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Media.Animation;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;
namespace TestProject
{
/// <summary>
/// 상세 페이지
/// </summary>
public sealed partial class DetailPage : Page
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 문화 정보
/// </summary>
private CultureInfo cultureInfo = CultureInfo.CurrentCulture;
/// <summary>
/// 이미지 파일 정보
/// </summary>
private ImageFileInfo imageFileInfo;
/// <summary>
/// 미저장 변경시 탐색 가능 여부
/// </summary>
private bool canNavigateWithUnsavedChanges = false;
/// <summary>
/// 비트맵 이미지
/// </summary>
private BitmapImage bitmapImage = null;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - DetailPage()
/// <summary>
/// 생성자
/// </summary>
public DetailPage()
{
InitializeComponent();
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Protected
//////////////////////////////////////////////////////////////////////////////// Function
#region 탐색되는 경우 처리하기 - OnNavigatedTo(e)
/// <summary>
/// 탐색되는 경우 처리하기
/// </summary>
/// <param name="e">이벤트 인자</param>
protected override async void OnNavigatedTo(NavigationEventArgs e)
{
this.imageFileInfo = e.Parameter as ImageFileInfo;
this.canNavigateWithUnsavedChanges = false;
this.bitmapImage = await this.imageFileInfo.GetBitmapImageAsyc();
if(this.imageFileInfo != null)
{
ConnectedAnimation animation = ConnectedAnimationService.GetForCurrentView().GetAnimation("itemAnimation");
if(animation != null)
{
this.targetImage.Source = this.bitmapImage;
animation.Completed += async (s, _) =>
{
await LoadBrushAsync();
await Task.Delay(300);
this.targetImage.Source = null;
};
animation.TryStart(this.targetImage);
}
if (this.bitmapImage.PixelHeight == 0 && this.bitmapImage.PixelWidth == 0)
{
this.editButton.IsEnabled = false;
this.zoomButton.IsEnabled = false;
};
}
if(Frame.CanGoBack)
{
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
}
else
{
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Collapsed;
}
base.OnNavigatedTo(e);
}
#endregion
#region 탐색하는 경우 처리하기 - OnNavigatingFrom(e)
/// <summary>
/// 탐색하는 경우 처리하기
/// </summary>
/// <param name="e">이벤트 인자</param>
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)
{
if(this.imageFileInfo.NeedsSaved && !this.canNavigateWithUnsavedChanges)
{
e.Cancel = true;
ShowSaveDialog(e);
}
else
{
if(e.NavigationMode == NavigationMode.Back)
{
ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("backAnimation", this.imageRectangle);
}
this.canNavigateWithUnsavedChanges = false;
base.OnNavigatingFrom(e);
}
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Private
//////////////////////////////////////////////////////////////////////////////// Event
#region 확대/축소 슬라이더 값 변경시 처리하기 - zoomSlider_ValueChanged(sender, e)
/// <summary>
/// 확대/축소 슬라이더 값 변경시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void zoomSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
if(this.imageScrollVierwer != null)
{
this.imageScrollVierwer.ChangeView(null, null, (float)e.NewValue);
}
}
#endregion
#region 이미지 스크롤 뷰어 값 변경시 처리하기 - imageScrollViewer_ViewChanged(sender, e)
/// <summary>
/// 이미지 스크롤 뷰어 값 변경시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void imageScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
this.zoomSlider.Value = ((ScrollViewer)sender).ZoomFactor;
}
#endregion
#region 작은 이미지 로드시 처리하기 - SmallImage_Loaded(sender, e)
/// <summary>
/// 작은 이미지 로드시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void SmallImage_Loaded(object sender, RoutedEventArgs e)
{
this.smallImage.Source = this.bitmapImage;
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Function
#region 화면에 맞추기 - FitToScreen()
/// <summary>
/// 화면에 맞추기
/// </summary>
private void FitToScreen()
{
float zoomFactor = (float)Math.Min
(
this.imageScrollVierwer.ActualWidth / this.imageFileInfo.ImageProperties.Width,
this.imageScrollVierwer.ActualHeight / this.imageFileInfo.ImageProperties.Height
);
this.imageScrollVierwer.ChangeView(null, null, zoomFactor);
}
#endregion
#region 실제 크기 표시하기 - ShowActualSize()
/// <summary>
/// 실제 크기 표시하기
/// </summary>
private void ShowActualSize()
{
this.imageScrollVierwer.ChangeView(null, null, 1);
}
#endregion
#region 편집 상태 토글하기 - ToggleEditState()
/// <summary>
/// 편집 상태 토글하기
/// </summary>
private void ToggleEditState()
{
this.mainSplitView.IsPaneOpen = !this.mainSplitView.IsPaneOpen;
}
#endregion
#region 효과 초기화하기 - ResetEffects()
/// <summary>
/// 효과 초기화하기
/// </summary>
private void ResetEffects()
{
this.imageFileInfo.Exposure = 0f;
this.imageFileInfo.Blur = 0f;
this.imageFileInfo.Tint = 0f;
this.imageFileInfo.Temperature = 0f;
this.imageFileInfo.Contrast = 0f;
this.imageFileInfo.Saturation = 1f;
}
#endregion
#region 브러시 로드하기 (비동기) - LoadBrushAsync()
/// <summary>
/// 브러시 로드하기 (비동기)
/// </summary>
/// <returns>태스크</returns>
private async Task LoadBrushAsync()
{
using(IRandomAccessStream stream = await this.imageFileInfo.ImageFile.OpenReadAsync())
{
this.customCompositionBrush.LoadImage(stream);
}
}
#endregion
#region 이미지 로드하기 (비동기) - LoadImageAsync(imageFile, replaceImage)
/// <summary>
/// 이미지 로드하기 (비동기)
/// </summary>
/// <param name="storageFile">저장소 파일</param>
/// <param name="replaceImage">이미지 대체 여부</param>
/// <returns>태스크</returns>
private async Task LoadImageAsync(StorageFile storageFile, bool replaceImage)
{
this.imageFileInfo.NeedsSaved = false;
ImageFileInfo newImageFileInfo = await MainPage.GetImageFileInfoAsync(storageFile);
ResetEffects();
int index = MainPage.CurrentPage.ImageFileInfoCollection.IndexOf(this.imageFileInfo);
this.imageFileInfo = newImageFileInfo;
Bindings.Update();
UnloadObject(imageRectangle);
FindName("imgRect");
await LoadBrushAsync();
if(replaceImage == true)
{
MainPage.CurrentPage.ImageFileInfoCollection.RemoveAt(index);
MainPage.CurrentPage.ImageFileInfoCollection.Insert(index, this.imageFileInfo);
}
else
{
MainPage.CurrentPage.ImageFileInfoCollection.Insert(index + 1, this.imageFileInfo);
}
MainPage.CurrentPage.UpdateCurrentImageFileInfo(this.imageFileInfo);
this.bitmapImage = await this.imageFileInfo.GetBitmapImageAsyc();
this.smallImage.Source = this.bitmapImage;
}
#endregion
#region 이미지 내보내기 - ExportImage()
/// <summary>
/// 이미지 내보내기
/// </summary>
private async void ExportImage()
{
CanvasDevice canvasDevice = CanvasDevice.GetSharedDevice();
using
(
CanvasRenderTarget canvasRenderTarget = new CanvasRenderTarget
(
canvasDevice,
this.imageFileInfo.ImageProperties.Width,
this.imageFileInfo.ImageProperties.Height,
96
)
)
{
using(IRandomAccessStream sourceStream = await this.imageFileInfo.ImageFile.OpenReadAsync())
{
using(CanvasBitmap canvasBitmap = await CanvasBitmap.LoadAsync(canvasRenderTarget, sourceStream, 96))
{
this.customCompositionBrush.SetSource(canvasBitmap);
using(CanvasDrawingSession canvasDrawingSession = canvasRenderTarget.CreateDrawingSession())
{
canvasDrawingSession.Clear(Windows.UI.Colors.Black);
ICanvasImage canvasImage = this.customCompositionBrush.Image;
canvasDrawingSession.DrawImage(canvasImage);
}
FileSavePicker fileSavePicker = new FileSavePicker()
{
SuggestedStartLocation = PickerLocationId.PicturesLibrary,
SuggestedSaveFile = this.imageFileInfo.ImageFile
};
fileSavePicker.FileTypeChoices.Add("JPEG files", new List<string>() { ".jpg" });
StorageFile storageFile = await fileSavePicker.PickSaveFileAsync();
if(storageFile != null)
{
using(IRandomAccessStream targetStream = await storageFile.OpenAsync(FileAccessMode.ReadWrite))
{
await canvasRenderTarget.SaveAsync(targetStream, CanvasBitmapFileFormat.Jpeg);
}
bool replace = false;
if(storageFile.IsEqual(this.imageFileInfo.ImageFile))
{
replace = true;
}
try
{
await LoadImageAsync(storageFile, replace);
}
catch(Exception exception)
{
if(exception.Message.Contains("0x80070323"))
{
await LoadImageAsync(storageFile, replace);
}
}
}
}
}
}
}
#endregion
#region 저장 대화 상자 표시하기 - ShowSaveDialog(e)
/// <summary>
/// 저장 대화 상자 표시하기
/// </summary>
/// <param name="e">이벤트 인자</param>
private async void ShowSaveDialog(NavigatingCancelEventArgs e)
{
ContentDialog saveDialog = new ContentDialog()
{
Title = "알림",
Content = "저장되지 않은 변경 사항이 있습니다.",
PrimaryButtonText = "이동",
SecondaryButtonText = "이동 취소"
};
ContentDialogResult result = await saveDialog.ShowAsync();
if(result == ContentDialogResult.Primary)
{
this.canNavigateWithUnsavedChanges = true;
ResetEffects();
Frame.Navigate(e.SourcePageType, e.Parameter);
}
}
#endregion
}
}
▶ MainPage.xaml
<Page x:Name="page" x:Class="TestProject.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:animations="using:Microsoft.Toolkit.Uwp.UI.Animations"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
xmlns:local="using:TestProject"
NavigationCacheMode="Enabled"
SizeChanged="{x:Bind Page_SizeChanged}">
<Page.Resources>
<x:Int32 x:Key="LargeItemMarginValueKey">8</x:Int32>
<Thickness x:Key="LargeItemMarginKey">8</Thickness>
<x:Int32 x:Key="SmallItemMarginKeyValueKey">0</x:Int32>
<Thickness x:Key="SmallItemMarginKey">0</Thickness>
<x:Int32 x:Key="DefaultWindowSidePaddingValueKey">16</x:Int32>
<Flyout x:Key="zoomFlyout">
<StackPanel>
<Slider Name="zoomSlider"
Margin="0 5 0 0"
TickFrequency="90"
SnapsTo="Ticks"
Minimum="180"
Maximum="540"
Value="270"
Header="그리드 항목 크기"
ValueChanged="{x:Bind Page_SizeChanged}" />
<ToggleSwitch Name="fitScreenToggleSwitch"
Header="화면 맞춤"
ToolTipService.ToolTip="Resize images to use available space."
Toggled="{x:Bind Page_SizeChanged}" />
</StackPanel>
</Flyout>
<ItemsPanelTemplate x:Key="imageGridViewItemsPanelTemplateKey">
<ItemsWrapGrid
HorizontalAlignment="Center"
Orientation="Horizontal" />
</ItemsPanelTemplate>
<DataTemplate x:Key="imageGridViewDefaultItemDataTemplateKey" x:DataType="local:ImageFileInfo">
<Grid
Margin="{StaticResource LargeItemMarginKey}"
Height="{Binding ItemSize, ElementName=page}"
Width="{Binding ItemSize, ElementName=page}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Name="itemImage"
Stretch="Uniform"
Opacity="0" />
<StackPanel Grid.Row="1"
Orientation="Vertical">
<TextBlock Text="{x:Bind ImageTitle, Mode=OneWay}"
Style="{StaticResource SubtitleTextBlockStyle}"
HorizontalAlignment="Center" />
<StackPanel
HorizontalAlignment="Center"
Orientation="Horizontal">
<TextBlock
Style="{StaticResource CaptionTextBlockStyle}"
HorizontalAlignment="Center"
Text="{x:Bind ImageFileType}" />
<TextBlock
Style="{StaticResource CaptionTextBlockStyle}"
Margin="8 0 0 0"
HorizontalAlignment="Center"
Text="{x:Bind ImageDimensions}" />
</StackPanel>
<muxc:RatingControl
IsReadOnly="True"
Value="{x:Bind ImageRating, Mode=OneWay}" />
</StackPanel>
</Grid>
</DataTemplate>
<Style x:Key="imageGridViewDefaultItemContainerStyleKey" TargetType="GridViewItem">
<Setter Property="Margin" Value="{StaticResource LargeItemMarginKey}" />
<Setter Property="Background" Value="Gray" />
</Style>
<DataTemplate x:Key="imageGridViewSmallItemDataTemplateKey" x:DataType="local:ImageFileInfo">
<Grid
Width="{Binding ItemSize, ElementName=page}"
Height="{Binding ItemSize, ElementName=page}">
<Image Name="itemImage"
Stretch="UniformToFill"
Opacity="0">
<ToolTipService.ToolTip>
<ToolTip Name="tooltip">
<StackPanel Grid.Row="1"
Orientation="Vertical">
<TextBlock
Style="{StaticResource SubtitleTextBlockStyle}"
HorizontalAlignment="Center"
Text="{x:Bind ImageTitle, Mode=OneWay}" />
<StackPanel
HorizontalAlignment="Center"
Orientation="Horizontal">
<TextBlock
Style="{StaticResource CaptionTextBlockStyle}"
HorizontalAlignment="Center"
Text="{x:Bind ImageFileType}" />
<TextBlock
Margin="8 0 0 0"
Style="{StaticResource CaptionTextBlockStyle}"
HorizontalAlignment="Center"
Text="{x:Bind ImageDimensions}" />
</StackPanel>
</StackPanel>
</ToolTip>
</ToolTipService.ToolTip>
</Image>
</Grid>
</DataTemplate>
<Style x:Key="imageGridViewSmallItemContainerStyleKey" TargetType="GridViewItem" />
</Page.Resources>
<RelativePanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Name="titleTextBlock"
Style="{StaticResource TitleTextBlockStyle}"
Margin="24 0 0 24"
Text="컬렉션" />
<CommandBar Name="mainCommandBar"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
RelativePanel.AlignRightWithPanel="True"
OverflowButtonVisibility="Collapsed"
DefaultLabelPosition="Right">
<AppBarButton
Icon="Zoom"
Label="확대/축소"
Flyout="{StaticResource zoomFlyout}" />
</CommandBar>
<GridView Name="imageGridView"
ItemsPanel="{StaticResource imageGridViewItemsPanelTemplateKey}"
ItemTemplate="{StaticResource imageGridViewDefaultItemDataTemplateKey}"
ItemContainerStyle="{StaticResource imageGridViewDefaultItemContainerStyleKey}"
Margin="0 0 0 8"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.Below="titleTextBlock"
animations:ReorderGridAnimation.Duration="400"
IsItemClickEnabled="True"
ItemsSource="{x:Bind ImageFileInfoCollection, Mode=OneWay}"
Loaded="{x:Bind imageGridView_Loaded}"
ContainerContentChanging="imageGridView_ContainerContentChanging"
ItemClick="imageGridView_ItemClick">
</GridView>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="{StaticResource LargeWindowBreakpointDoubleKey}" />
</VisualState.StateTriggers>
</VisualState>
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="{StaticResource MediumWindowBreakpointDoubleKey}" />
</VisualState.StateTriggers>
</VisualState>
<VisualState>
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowWidth="{StaticResource MinimumWindowBreakpointDoubleKey}" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="imageGridView.ItemTemplate" Value="{StaticResource imageGridViewSmallItemDataTemplateKey}" />
<Setter Target="imageGridView.ItemContainerStyle" Value="{StaticResource imageGridViewSmallItemContainerStyleKey}" />
<Setter Target="zoomSlider.Minimum" Value="80" />
<Setter Target="zoomSlider.Maximum" Value="180" />
<Setter Target="zoomSlider.TickFrequency" Value="20" />
<Setter Target="zoomSlider.Value" Value="100" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</RelativePanel>
</Page>
▶ MainPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Storage;
using Windows.Storage.FileProperties;
using Windows.Storage.Search;
using Windows.UI.Core;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media.Animation;
using Windows.UI.Xaml.Media.Imaging;
using Windows.UI.Xaml.Navigation;
namespace TestProject
{
/// <summary>
/// 메인 페이지
/// </summary>
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Event
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 속성 변경시 이벤트 - PropertyChanged
/// <summary>
/// 속성 변경시 이벤트
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region Field
/// <summary>
/// 현재 페이지
/// </summary>
public static MainPage CurrentPage;
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Instance
//////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 항목 크기
/// </summary>
private double itemSize;
/// <summary>
/// 현재 이미지 파일 정보
/// </summary>
private ImageFileInfo currentImageFileInfo;
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Instance
//////////////////////////////////////////////////////////////////////////////// Private
#region 이미지 파일 정보 컬렉션 - ImageFileInfoCollection
/// <summary>
/// 이미지 파일 정보 컬렉션
/// </summary>
public ObservableCollection<ImageFileInfo> ImageFileInfoCollection { get; } = new ObservableCollection<ImageFileInfo>();
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 항목 크기 - ItemSize
/// <summary>
/// 항목 크기
/// </summary>
public double ItemSize
{
get => this.itemSize;
set
{
if(this.itemSize != value)
{
this.itemSize = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ItemSize)));
}
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - MainPage()
/// <summary>
/// 생성자
/// </summary>
public MainPage()
{
InitializeComponent();
CurrentPage = this;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 현재 이미지 파일 정보 업데이트하기 - UpdateCurrentImageFileInfo(imageFileInfo)
/// <summary>
/// 현재 이미지 파일 정보 업데이트하기
/// </summary>
/// <param name="imageFileInfo">이미지 파일 정보</param>
public void UpdateCurrentImageFileInfo(ImageFileInfo imageFileInfo)
{
this.currentImageFileInfo = imageFileInfo;
}
#endregion
#region 이미지 파일 정보 구하기 (비동기) - GetImageFileInfoAsync(storageFile)
/// <summary>
/// 이미지 파일 정보 구하기 (비동기)
/// </summary>
/// <param name="storageFile">저장소 파일</param>
/// <returns>이미지 파일 정보 태스크</returns>
public async static Task<ImageFileInfo> GetImageFileInfoAsync(StorageFile storageFile)
{
ImageProperties imageProperties = await storageFile.Properties.GetImagePropertiesAsync();
ImageFileInfo imageFileInfo = new ImageFileInfo
(
imageProperties,
storageFile,
storageFile.DisplayName,
storageFile.DisplayType
);
return imageFileInfo;
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Protected
//////////////////////////////////////////////////////////////////////////////// Function
#region 탐색되는 경우 처리하기 - OnNavigatedTo(e)
/// <summary>
/// 탐색되는 경우 처리하기
/// </summary>
/// <param name="e">이벤트 인자</param>
protected async override void OnNavigatedTo(NavigationEventArgs e)
{
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Collapsed;
if(ImageFileInfoCollection.Count == 0)
{
await AddImageFileInfoAsync();
}
base.OnNavigatedTo(e);
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Private
//////////////////////////////////////////////////////////////////////////////// Event
#region 페이지 크기 변경시 처리하기 - Page_SizeChanged()
/// <summary>
/// 페이지 크기 변경시 처리하기
/// </summary>
private void Page_SizeChanged()
{
if(this.fitScreenToggleSwitch != null && this.fitScreenToggleSwitch.IsOn == true && this.imageGridView != null && this.zoomSlider != null)
{
int margin = (int)Resources["LargeItemMarginValueKey"] * 4;
double gridWidth = this.imageGridView.ActualWidth - (int)Resources["DefaultWindowSidePaddingValueKey"];
double ItemWidth = this.zoomSlider.Value + margin;
int columnCount = (int)Math.Max(gridWidth / ItemWidth, 1);
double adjustedGridWidth = gridWidth - (columnCount * margin);
ItemSize = (adjustedGridWidth / columnCount);
}
else
{
ItemSize = this.zoomSlider.Value;
}
}
#endregion
#region 이미지 그리드 뷰 로드시 처리하기 - imageGridView_Loaded()
/// <summary>
/// 이미지 그리드 뷰 로드시 처리하기
/// </summary>
private async void imageGridView_Loaded()
{
// 세부 정보 페이지에서 기본 페이지로 다시 탐색하기 위해 연결된 애니메이션을 실행한다.
if(this.currentImageFileInfo != null)
{
this.imageGridView.ScrollIntoView(this.currentImageFileInfo);
ConnectedAnimation animation = ConnectedAnimationService.GetForCurrentView().GetAnimation("backAnimation");
if(animation != null)
{
await this.imageGridView.TryStartConnectedAnimationAsync(animation, this.currentImageFileInfo, "itemImage");
}
}
}
#endregion
#region 이미지 그리드 뷰 컨테이너 내용 변경시 처리하기 - imageGridView_ContainerContentChanging(sender, e)
/// <summary>
/// 이미지 그리드 뷰 컨테이너 내용 변경시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void imageGridView_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs e)
{
if(e.InRecycleQueue)
{
Grid rootGrid = e.ItemContainer.ContentTemplateRoot as Grid;
Image image = (Image)rootGrid.FindName("itemImage");
image.Source = null;
}
if(e.Phase == 0)
{
e.RegisterUpdateCallback(ShowImage);
e.Handled = true;
}
}
#endregion
#region 이미지 그리드 뷰 항목 클릭시 처리하기 - imageGridView_ItemClick(sender, e)
/// <summary>
/// 이미지 그리드 뷰 항목 클릭시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void imageGridView_ItemClick(object sender, ItemClickEventArgs e)
{
this.currentImageFileInfo = e.ClickedItem as ImageFileInfo;
this.imageGridView.PrepareConnectedAnimation("itemAnimation", e.ClickedItem, "itemImage");
Frame.Navigate(typeof(DetailPage), e.ClickedItem);
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Function
#region 이미지 파일 정보 추가하기 (비동기) - AddImageFileInfoAsync()
/// <summary>
/// 이미지 파일 정보 추가하기 (비동기)
/// </summary>
/// <returns>태스크</returns>
private async Task AddImageFileInfoAsync()
{
QueryOptions options = new QueryOptions();
options.FolderDepth = FolderDepth.Deep;
options.FileTypeFilter.Add(".jpg");
options.FileTypeFilter.Add(".png");
options.FileTypeFilter.Add(".gif");
StorageFolder applicationFolder = Package.Current.InstalledLocation;
StorageFolder sampleFolder = await applicationFolder.GetFolderAsync("Asset\\Sample");
StorageFileQueryResult queryResult = sampleFolder.CreateFileQueryWithOptions(options);
IReadOnlyList<StorageFile> fileList = await queryResult.GetFilesAsync();
bool unsupportedFilesFound = false;
foreach(StorageFile storageFile in fileList)
{
if(storageFile.Provider.Id == "computer")
{
ImageFileInfoCollection.Add(await GetImageFileInfoAsync(storageFile));
}
else
{
unsupportedFilesFound = true;
}
}
if(unsupportedFilesFound == true)
{
ContentDialog contentDialog = new ContentDialog
{
Title = "미지원 이미지 발견",
Content = "이 샘플 앱은 컴퓨터에 로컬로 저장된 이미지만 지원합니다." +
" 라이브러리에서 OneDrive 또는 다른 네트워크 위치에 저장된 파일을 찾았습니다." +
" 해당 이미지를 로드하지 않았습니다.",
CloseButtonText = "확인"
};
ContentDialogResult dialogResult = await contentDialog.ShowAsync();
}
}
#endregion
#region 이미지 표시하기 - ShowImage(sender, e)
/// <summary>
/// 이미지 표시하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private async void ShowImage(ListViewBase sender, ContainerContentChangingEventArgs e)
{
if(e.Phase == 1)
{
Grid rootGrid = e.ItemContainer.ContentTemplateRoot as Grid;
Image image = (Image)rootGrid.FindName("itemImage");
image.Opacity = 100;
ImageFileInfo imageFileInfo = e.Item as ImageFileInfo;
try
{
image.Source = await imageFileInfo.GetThumbnailBitmapImageAsync();
}
catch(Exception)
{
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.UriSource = new Uri(image.BaseUri, "Asset/StoreLogo.png");
image.Source = bitmapImage;
}
}
}
#endregion
}
}
▶ App.xaml
<Application x:Class="TestProject.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
</ResourceDictionary.MergedDictionaries>
<x:Double x:Key="MinimumWindowBreakpointDoubleKey">0</x:Double>
<x:Double x:Key="MediumWindowBreakpointDoubleKey">641</x:Double>
<x:Double x:Key="LargeWindowBreakpointDoubleKey">1008</x:Double>
</ResourceDictionary>
</Application.Resources>
</Application>
▶ App.xaml.cs
using System;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Graphics.Display;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
namespace TestProject
{
/// <summary>
/// 앱
/// </summary>
sealed partial class App : Application
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - App()
/// <summary>
/// 생성자
/// </summary>
public App()
{
InitializeComponent();
Suspending += Application_Suspending;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region 시작시 처리하기 - OnLaunched(e)
/// <summary>
/// 시작시 처리하기
/// </summary>
/// <param name="e">이벤트 인자</param>
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
SystemNavigationManager.GetForCurrentView().BackRequested += SystemNavigationManager_BackRequested;
Frame rootFrame = Window.Current.Content as Frame;
if(rootFrame == null)
{
rootFrame = new Frame();
rootFrame.NavigationFailed += rootFrame_NavigationFailed;
if(e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{
}
Window.Current.Content = rootFrame;
}
if(e.PrelaunchActivated == false)
{
if(rootFrame.Content == null)
{
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
Window.Current.Activate();
}
#region 윈도우 크기를 설정한다.
double width = 800d;
double height = 600d;
double dpi = (double)DisplayInformation.GetForCurrentView().LogicalDpi;
ApplicationView.PreferredLaunchWindowingMode = ApplicationViewWindowingMode.PreferredLaunchViewSize;
Size windowSize = new Size(width * 96d / dpi, height * 96d / dpi);
ApplicationView.PreferredLaunchViewSize = windowSize;
Window.Current.Activate();
ApplicationView.GetForCurrentView().TryResizeView(windowSize);
#endregion
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region 애플리케이션 보류시 처리하기 - Application_Suspending(sender, e)
/// <summary>
/// 애플리케이션 보류시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void Application_Suspending(object sender, SuspendingEventArgs e)
{
SuspendingDeferral deferral = e.SuspendingOperation.GetDeferral();
deferral.Complete();
}
#endregion
#region 시스템 탐색 관리자 뒤로 이동 요청시 처리하기 - SystemNavigationManager_BackRequested(sender, e)
/// <summary>
/// 시스템 탐색 관리자 뒤로 이동 요청시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void SystemNavigationManager_BackRequested(object sender, BackRequestedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
if(rootFrame == null)
{
return;
}
if(rootFrame.CanGoBack && e.Handled == false)
{
e.Handled = true;
rootFrame.GoBack();
}
}
#endregion
#region 루트 프레임 탐색 실패시 처리하기 - rootFrame_NavigationFailed(sender, e)
/// <summary>
/// 루트 프레임 탐색 실패시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void rootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
{
throw new Exception($"페이지 로드를 실패했습니다 : {e.SourcePageType.FullName}");
}
#endregion
}
}
728x90
반응형
그리드형(광고전용)
'C# > UWP' 카테고리의 다른 글
[C#/UWP] SystemNavigationManager 클래스 : BackRequested 이벤트 사용하기 (0) | 2021.06.03 |
---|---|
[C#/UWP] ConnectedAnimation 클래스 : 페이지 이동시 애니메이션 사용하기 (0) | 2021.06.02 |
[C#/UWP] ItemsReorderAnimation 클래스 : SetDuration 정적 메소드를 사용해 GridView 항목 애니메이션 사용하기 (0) | 2021.06.02 |
[C#/UWP] 태스크 모니터 사용하기 (0) | 2021.05.31 |
[C#/UWP] ControlTemplate 엘리먼트 : Slider 엘리먼트 정의하기 (0) | 2021.05.19 |
[C#/UWP] Launcher 클래스 : QueryUriSupportAsync 정적 메소드를 사용해 UWP 앱 설치 여부 구하기 (0) | 2021.05.11 |
[C#/UWP] LockScreen 클래스 : SetImageFileAsync 정적 메소드를 사용해 잠금 화면 이미지 설정하기 (0) | 2021.05.05 |
[C#/UWP] FileOpenPicker 클래스 : PickSingleFileAsync 메소드를 사용해 파일 선택하기 (0) | 2021.05.05 |
[C#/UWP] ContentDialog 클래스 : ShowAsync 메소드를 사용해 대화 상자 표시하기 (0) | 2021.05.04 |
[C#/UWP] StorageFolder 클래스 : GetFolderAsync 메소드를 사용해 자식 저장소 폴더 구하기 (0) | 2021.05.04 |
댓글을 달아 주세요