[Color Picker] remove and export multiple colors from history (#16475)

* Allow to export and delete multiple colors

* Allow to export and delete multiple colors
This commit is contained in:
mshtang 2022-03-07 12:47:42 +01:00 committed by GitHub
parent e8363a3be1
commit 81f61630cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 331 additions and 28 deletions

View File

@ -79,9 +79,13 @@ namespace ColorPicker.Helpers
/// <param name="color">The see cref="Color"/> for the hexadecimal presentation</param> /// <param name="color">The see cref="Color"/> for the hexadecimal presentation</param>
/// <returns>A hexadecimal <see cref="string"/> representation of a RGB color</returns> /// <returns>A hexadecimal <see cref="string"/> representation of a RGB color</returns>
private static string ColorToHex(Color color) private static string ColorToHex(Color color)
=> $"{color.R.ToString("x2", CultureInfo.InvariantCulture)}" {
+ $"{color.G.ToString("x2", CultureInfo.InvariantCulture)}" const string hexFormat = "x2";
+ $"{color.B.ToString("x2", CultureInfo.InvariantCulture)}";
return $"{color.R.ToString(hexFormat, CultureInfo.InvariantCulture)}"
+ $"{color.G.ToString(hexFormat, CultureInfo.InvariantCulture)}"
+ $"{color.B.ToString(hexFormat, CultureInfo.InvariantCulture)}";
}
/// <summary> /// <summary>
/// Return a <see cref="string"/> representation of a HSB color /// Return a <see cref="string"/> representation of a HSB color
@ -109,9 +113,12 @@ namespace ColorPicker.Helpers
private static string ColorToFloat(Color color) private static string ColorToFloat(Color color)
{ {
var (red, green, blue) = ColorHelper.ConvertToDouble(color); var (red, green, blue) = ColorHelper.ConvertToDouble(color);
var precision = 2; const int precision = 2;
const string floatFormat = "0.##";
return $"({Math.Round(red, precision).ToString("0.##", CultureInfo.InvariantCulture)}f, {Math.Round(green, precision).ToString("0.##", CultureInfo.InvariantCulture)}f, {Math.Round(blue, precision).ToString("0.##", CultureInfo.InvariantCulture)}f, 1f)"; return $"({Math.Round(red, precision).ToString(floatFormat, CultureInfo.InvariantCulture)}f"
+ $", {Math.Round(green, precision).ToString(floatFormat, CultureInfo.InvariantCulture)}f"
+ $", {Math.Round(blue, precision).ToString(floatFormat, CultureInfo.InvariantCulture)}f, 1f)";
} }
/// <summary> /// <summary>

View File

@ -0,0 +1,131 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json;
using System.Windows.Media;
using ColorPicker.Models;
namespace ColorPicker.Helpers
{
internal enum GroupExportedColorsBy
{
Color,
Format,
}
internal static class SerializationHelper
{
public static Dictionary<string, Dictionary<string, string>> ConvertToDesiredColorFormats(
IList colorsToExport,
IEnumerable<ColorFormatModel> colorRepresentations,
GroupExportedColorsBy method)
{
var colors = new Dictionary<string, Dictionary<string, string>>();
var colorFormatModels = colorRepresentations.ToList();
var i = 1;
switch (method)
{
case GroupExportedColorsBy.Color:
{
foreach (Color color in (IList)colorsToExport)
{
var tmp = new Dictionary<string, string>();
foreach (var colorFormatModel in colorFormatModels)
{
var colorInSpecificFormat = colorFormatModel.Convert(color);
if (colorFormatModel.FormatName == "HEX")
{
colorInSpecificFormat = "#" + colorInSpecificFormat;
}
tmp.Add(
colorFormatModel.FormatName,
#pragma warning disable CA1308 // Normalize strings to uppercase
colorInSpecificFormat.Replace(
colorFormatModel.FormatName.ToLower(CultureInfo.InvariantCulture),
string.Empty,
StringComparison.InvariantCultureIgnoreCase));
#pragma warning restore CA1308 // Normalize strings to uppercase
}
colors.Add($"color{i++}", tmp);
}
}
break;
case GroupExportedColorsBy.Format:
{
foreach (var colorFormatModel in colorFormatModels)
{
var tmp = new Dictionary<string, string>();
i = 1;
foreach (Color color in (IList)colorsToExport)
{
var colorInSpecificFormat = colorFormatModel.Convert(color);
if (colorFormatModel.FormatName == "HEX")
{
colorInSpecificFormat = "#" + colorInSpecificFormat;
}
tmp.Add(
$"color{i++}",
#pragma warning disable CA1308 // Normalize strings to uppercase
colorInSpecificFormat.Replace(
colorFormatModel.FormatName.ToLower(CultureInfo.InvariantCulture),
string.Empty,
StringComparison.InvariantCultureIgnoreCase));
#pragma warning restore CA1308 // Normalize strings to uppercase
}
colors.Add(colorFormatModel.FormatName, tmp);
}
}
break;
}
return colors;
}
public static string ToTxt(this Dictionary<string, Dictionary<string, string>> source, char separator)
{
var res = string.Empty;
foreach (var (key, val) in source)
{
res += $"{key}{separator}";
res = val.Aggregate(res, (current, pair) =>
{
var (p, v) = pair;
// if grouped by format, add a space between color* and its value, to avoid illegibility:
// decimal;color1 12345678;color2 23456789;color11 13579246
if (key == "Decimal")
{
v = " " + v;
}
return current + $"{p}{v}{separator}";
});
res = res.TrimEnd(separator) + System.Environment.NewLine;
}
return res;
}
public static string ToJson(this Dictionary<string, Dictionary<string, string>> source, bool indented = true)
{
var options = new JsonSerializerOptions
{
WriteIndented = indented,
};
return JsonSerializer.Serialize(source, options);
}
}
}

View File

@ -19,7 +19,7 @@ namespace ColorPicker.Properties {
// class via a tool like ResGen or Visual Studio. // class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen // To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project. // with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources { public class Resources {
@ -159,6 +159,33 @@ namespace ColorPicker.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to Export.
/// </summary>
public static string Export_by {
get {
return ResourceManager.GetString("Export_by", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Grouped by color.
/// </summary>
public static string Export_by_color {
get {
return ResourceManager.GetString("Export_by_color", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Grouped by format.
/// </summary>
public static string Export_by_format {
get {
return ResourceManager.GetString("Export_by_format", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Green value. /// Looks up a localized string similar to Green value.
/// </summary> /// </summary>

View File

@ -173,6 +173,18 @@
<value>Remove</value> <value>Remove</value>
<comment>Tooltip that shows that the user can remove an item from a list</comment> <comment>Tooltip that shows that the user can remove an item from a list</comment>
</data> </data>
<data name="Export_by" xml:space="preserve">
<value>Export</value>
<comment>Tooltip that shows the user can export the selected items to a file</comment>
</data>
<data name="Export_by_color" xml:space="preserve">
<value>Grouped by color</value>
<comment>Tooltip that shows the user can export the selected items grouped by color to a file</comment>
</data>
<data name="Export_by_format" xml:space="preserve">
<value>Grouped by format</value>
<comment>Tooltip that shows the user can export the selected items grouped by format to a file</comment>
</data>
<data name="Saturation_slider" xml:space="preserve"> <data name="Saturation_slider" xml:space="preserve">
<value>Saturation slider</value> <value>Saturation slider</value>
<comment>Tool tip that appears when hovering over a slider that represents the color saturation (from HSV)</comment> <comment>Tool tip that appears when hovering over a slider that represents the color saturation (from HSV)</comment>

View File

@ -29,6 +29,8 @@ namespace ColorPicker.Telemetry
public bool EditorColorAdjusted { get; set; } public bool EditorColorAdjusted { get; set; }
public bool EditorColorsExported { get; set; }
public bool EditorSimilarColorPicked { get; set; } public bool EditorSimilarColorPicked { get; set; }
public bool EditorHistoryColorPicked { get; set; } public bool EditorHistoryColorPicked { get; set; }

View File

@ -20,7 +20,11 @@ namespace ColorPicker.ViewModelContracts
ICommand OpenSettingsCommand { get; } ICommand OpenSettingsCommand { get; }
ICommand RemoveColorCommand { get; } ICommand RemoveColorsCommand { get; }
ICommand ExportColorsGroupedByColorCommand { get; }
ICommand ExportColorsGroupedByFormatCommand { get; }
ObservableCollection<ColorFormatModel> ColorRepresentations { get; } ObservableCollection<ColorFormatModel> ColorRepresentations { get; }

View File

@ -3,10 +3,13 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.Drawing.Drawing2D;
using System.Globalization; using System.Globalization;
using System.IO;
using System.Linq; using System.Linq;
using System.Windows.Input; using System.Windows.Input;
using System.Windows.Media; using System.Windows.Media;
@ -16,6 +19,7 @@ using ColorPicker.Models;
using ColorPicker.Settings; using ColorPicker.Settings;
using ColorPicker.ViewModelContracts; using ColorPicker.ViewModelContracts;
using Microsoft.PowerToys.Settings.UI.Library.Enumerations; using Microsoft.PowerToys.Settings.UI.Library.Enumerations;
using Microsoft.Win32;
namespace ColorPicker.ViewModels namespace ColorPicker.ViewModels
{ {
@ -33,10 +37,17 @@ namespace ColorPicker.ViewModels
{ {
OpenColorPickerCommand = new RelayCommand(() => OpenColorPickerRequested?.Invoke(this, EventArgs.Empty)); OpenColorPickerCommand = new RelayCommand(() => OpenColorPickerRequested?.Invoke(this, EventArgs.Empty));
OpenSettingsCommand = new RelayCommand(() => OpenSettingsRequested?.Invoke(this, EventArgs.Empty)); OpenSettingsCommand = new RelayCommand(() => OpenSettingsRequested?.Invoke(this, EventArgs.Empty));
RemoveColorCommand = new RelayCommand(DeleteSelectedColor);
RemoveColorsCommand = new RelayCommand(DeleteSelectedColors);
ExportColorsGroupedByColorCommand = new RelayCommand(ExportSelectedColorsByColor);
ExportColorsGroupedByFormatCommand = new RelayCommand(ExportSelectedColorsByFormat);
SelectedColorChangedCommand = new RelayCommand((newColor) => SelectedColorChangedCommand = new RelayCommand((newColor) =>
{ {
if (ColorsHistory.Contains((Color)newColor))
{
ColorsHistory.Remove((Color)newColor);
}
ColorsHistory.Insert(0, (Color)newColor); ColorsHistory.Insert(0, (Color)newColor);
SelectedColorIndex = 0; SelectedColorIndex = 0;
}); });
@ -54,7 +65,11 @@ namespace ColorPicker.ViewModels
public ICommand OpenSettingsCommand { get; } public ICommand OpenSettingsCommand { get; }
public ICommand RemoveColorCommand { get; } public ICommand RemoveColorsCommand { get; }
public ICommand ExportColorsGroupedByColorCommand { get; }
public ICommand ExportColorsGroupedByFormatCommand { get; }
public ICommand SelectedColorChangedCommand { get; } public ICommand SelectedColorChangedCommand { get; }
@ -133,15 +148,99 @@ namespace ColorPicker.ViewModels
} }
} }
private void DeleteSelectedColor() private void DeleteSelectedColors(object selectedColors)
{ {
// select new color on the same index if possible, otherwise the last one var colorsToRemove = ((IList)selectedColors).OfType<Color>().ToList();
var indexToSelect = SelectedColorIndex == ColorsHistory.Count - 1 ? ColorsHistory.Count - 2 : SelectedColorIndex; var indicesToRemove = colorsToRemove.Select(color => ColorsHistory.IndexOf(color)).ToList();
ColorsHistory.RemoveAt(SelectedColorIndex);
SelectedColorIndex = indexToSelect; foreach (var color in colorsToRemove)
{
ColorsHistory.Remove(color);
}
SelectedColorIndex = ComputeWhichIndexToSelectAfterDeletion(colorsToRemove.Count + ColorsHistory.Count, indicesToRemove);
SessionEventHelper.Event.EditorHistoryColorRemoved = true; SessionEventHelper.Event.EditorHistoryColorRemoved = true;
} }
private void ExportSelectedColorsByColor(object selectedColors)
{
ExportColors(selectedColors, GroupExportedColorsBy.Color);
}
private void ExportSelectedColorsByFormat(object selectedColors)
{
ExportColors(selectedColors, GroupExportedColorsBy.Format);
}
private void ExportColors(object colorsToExport, GroupExportedColorsBy method)
{
var colors = SerializationHelper.ConvertToDesiredColorFormats((IList)colorsToExport, ColorRepresentations, method);
var dialog = new SaveFileDialog
{
Title = "Save selected colors to",
Filter = "Text Files (*.txt)|*.txt|Json Files (*.json)|*.json",
};
if (dialog.ShowDialog() == true)
{
var extension = Path.GetExtension(dialog.FileName);
var contentToWrite = extension.ToUpperInvariant() switch
{
".TXT" => colors.ToTxt(';'),
".JSON" => colors.ToJson(),
_ => string.Empty
};
File.WriteAllText(dialog.FileName, contentToWrite);
SessionEventHelper.Event.EditorColorsExported = true;
}
}
// Will select the closest color to the last selected one in color history
private static int ComputeWhichIndexToSelectAfterDeletion(int colorsCount, List<int> indicesToRemove)
{
var newIndices = Enumerable.Range(0, colorsCount).ToList();
foreach (var index in indicesToRemove)
{
newIndices[index] = -1;
}
var appearancesOfMinusOne = 0;
for (var i = 0; i < newIndices.Count; i++)
{
if (newIndices[i] < 0)
{
appearancesOfMinusOne++;
continue;
}
newIndices[i] -= appearancesOfMinusOne;
}
var lastSelectedIndex = indicesToRemove.Last();
for (int i = lastSelectedIndex - 1, j = lastSelectedIndex + 1; ; i--, j++)
{
if (j < newIndices.Count && newIndices[j] != -1)
{
return newIndices[j];
}
if (i >= 0 && newIndices[i] != -1)
{
return newIndices[i];
}
if (j >= newIndices.Count && i < 0)
{
break;
}
}
return -1;
}
private void SetupAllColorRepresentations() private void SetupAllColorRepresentations()
{ {
_allColorRepresentations.Add( _allColorRepresentations.Add(
@ -149,7 +248,7 @@ namespace ColorPicker.ViewModels
{ {
FormatName = ColorRepresentationType.HEX.ToString(), FormatName = ColorRepresentationType.HEX.ToString(),
#pragma warning disable CA1304 // Specify CultureInfo #pragma warning disable CA1304 // Specify CultureInfo
Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.HEX).ToLower(); }, Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.HEX).ToLower(),
#pragma warning restore CA1304 // Specify CultureInfo #pragma warning restore CA1304 // Specify CultureInfo
}); });
@ -157,76 +256,76 @@ namespace ColorPicker.ViewModels
new ColorFormatModel() new ColorFormatModel()
{ {
FormatName = ColorRepresentationType.RGB.ToString(), FormatName = ColorRepresentationType.RGB.ToString(),
Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.RGB); }, Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.RGB),
}); });
_allColorRepresentations.Add( _allColorRepresentations.Add(
new ColorFormatModel() new ColorFormatModel()
{ {
FormatName = ColorRepresentationType.HSL.ToString(), FormatName = ColorRepresentationType.HSL.ToString(),
Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.HSL); }, Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.HSL),
}); });
_allColorRepresentations.Add( _allColorRepresentations.Add(
new ColorFormatModel() new ColorFormatModel()
{ {
FormatName = ColorRepresentationType.HSV.ToString(), FormatName = ColorRepresentationType.HSV.ToString(),
Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.HSV); }, Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.HSV),
}); });
_allColorRepresentations.Add( _allColorRepresentations.Add(
new ColorFormatModel() new ColorFormatModel()
{ {
FormatName = ColorRepresentationType.CMYK.ToString(), FormatName = ColorRepresentationType.CMYK.ToString(),
Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.CMYK); }, Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.CMYK),
}); });
_allColorRepresentations.Add( _allColorRepresentations.Add(
new ColorFormatModel() new ColorFormatModel()
{ {
FormatName = ColorRepresentationType.HSB.ToString(), FormatName = ColorRepresentationType.HSB.ToString(),
Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.HSB); }, Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.HSB),
}); });
_allColorRepresentations.Add( _allColorRepresentations.Add(
new ColorFormatModel() new ColorFormatModel()
{ {
FormatName = ColorRepresentationType.HSI.ToString(), FormatName = ColorRepresentationType.HSI.ToString(),
Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.HSI); }, Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.HSI),
}); });
_allColorRepresentations.Add( _allColorRepresentations.Add(
new ColorFormatModel() new ColorFormatModel()
{ {
FormatName = ColorRepresentationType.HWB.ToString(), FormatName = ColorRepresentationType.HWB.ToString(),
Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.HWB); }, Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.HWB),
}); });
_allColorRepresentations.Add( _allColorRepresentations.Add(
new ColorFormatModel() new ColorFormatModel()
{ {
FormatName = ColorRepresentationType.NCol.ToString(), FormatName = ColorRepresentationType.NCol.ToString(),
Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.NCol); }, Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.NCol),
}); });
_allColorRepresentations.Add( _allColorRepresentations.Add(
new ColorFormatModel() new ColorFormatModel()
{ {
FormatName = ColorRepresentationType.CIELAB.ToString(), FormatName = ColorRepresentationType.CIELAB.ToString(),
Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.CIELAB); }, Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.CIELAB),
}); });
_allColorRepresentations.Add( _allColorRepresentations.Add(
new ColorFormatModel() new ColorFormatModel()
{ {
FormatName = ColorRepresentationType.CIEXYZ.ToString(), FormatName = ColorRepresentationType.CIEXYZ.ToString(),
Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.CIEXYZ); }, Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.CIEXYZ),
}); });
_allColorRepresentations.Add( _allColorRepresentations.Add(
new ColorFormatModel() new ColorFormatModel()
{ {
FormatName = ColorRepresentationType.VEC4.ToString(), FormatName = ColorRepresentationType.VEC4.ToString(),
Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.VEC4); }, Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.VEC4),
}); });
_allColorRepresentations.Add( _allColorRepresentations.Add(
new ColorFormatModel() new ColorFormatModel()
{ {
FormatName = "Decimal", FormatName = "Decimal",
Convert = (Color color) => { return ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.DecimalValue); }, Convert = (Color color) => ColorRepresentationHelper.GetStringRepresentationFromMediaColor(color, ColorRepresentationType.DecimalValue),
}); });
_userSettings.VisibleColorFormats.CollectionChanged += VisibleColorFormats_CollectionChanged; _userSettings.VisibleColorFormats.CollectionChanged += VisibleColorFormats_CollectionChanged;

View File

@ -33,15 +33,36 @@
SelectedIndex="{Binding SelectedColorIndex}" SelectedIndex="{Binding SelectedColorIndex}"
ItemContainerStyle="{DynamicResource ColorHistoryListViewStyle}" ItemContainerStyle="{DynamicResource ColorHistoryListViewStyle}"
IsItemClickEnabled="True" IsItemClickEnabled="True"
SelectionMode="Extended"
ItemClick="HistoryColors_ItemClick"> ItemClick="HistoryColors_ItemClick">
<ui:ListView.ContextMenu> <ui:ListView.ContextMenu>
<ContextMenu Visibility="{Binding ColorsHistory.Count, Converter={StaticResource numberToVisibilityConverter}}"> <ContextMenu Visibility="{Binding ColorsHistory.Count, Converter={StaticResource numberToVisibilityConverter}}">
<MenuItem Header="{x:Static p:Resources.Remove}" <MenuItem Header="{x:Static p:Resources.Remove}"
Command="{Binding RemoveColorCommand}"> Command="{Binding RemoveColorsCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.SelectedItems}">
<MenuItem.Icon> <MenuItem.Icon>
<ui:FontIcon Glyph="&#xE107;" /> <ui:FontIcon Glyph="&#xE107;" />
</MenuItem.Icon> </MenuItem.Icon>
</MenuItem> </MenuItem>
<MenuItem Header="{x:Static p:Resources.Export_by}" >
<MenuItem.Icon>
<ui:FontIcon Glyph="&#xEDE1;"/>
</MenuItem.Icon>
<MenuItem Header="{x:Static p:Resources.Export_by_color}"
Command="{Binding ExportColorsGroupedByColorCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.SelectedItems}">
<MenuItem.Icon>
<ui:FontIcon Glyph="&#xE790;"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{x:Static p:Resources.Export_by_format}"
Command="{Binding ExportColorsGroupedByFormatCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.SelectedItems}" >
<MenuItem.Icon>
<ui:FontIcon Glyph="&#xE943;"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</ContextMenu> </ContextMenu>
</ui:ListView.ContextMenu> </ui:ListView.ContextMenu>
<ui:ListView.ItemTemplate> <ui:ListView.ItemTemplate>