commit f965f46fb3ff313e26d94aacd044ec3b4a5e456c Author: Chris Kaczor Date: Wed Apr 30 17:33:21 2014 -0400 Initial commit diff --git a/CheckedListItem.cs b/CheckedListItem.cs new file mode 100644 index 0000000..a657a53 --- /dev/null +++ b/CheckedListItem.cs @@ -0,0 +1,50 @@ +using System.ComponentModel; + +namespace Common.Wpf +{ + public class CheckedListItem : INotifyPropertyChanged + { + public event PropertyChangedEventHandler PropertyChanged; + + private bool _isChecked; + private T _item; + + public CheckedListItem() { } + + public CheckedListItem(T item, bool isChecked = false) + { + _item = item; + _isChecked = isChecked; + } + + public T Item + { + get + { + return _item; + } + set + { + _item = value; + + if (PropertyChanged != null) + PropertyChanged(this, new PropertyChangedEventArgs("Item")); + } + } + + public bool IsChecked + { + get + { + return _isChecked; + } + set + { + _isChecked = value; + + if (PropertyChanged != null) + PropertyChanged(this, new PropertyChangedEventArgs("IsChecked")); + } + } + } +} diff --git a/Common.Wpf.csproj b/Common.Wpf.csproj new file mode 100644 index 0000000..a1aaaeb --- /dev/null +++ b/Common.Wpf.csproj @@ -0,0 +1,245 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {0074C983-550E-4094-9E8C-F566FB669297} + library + Properties + Common.Wpf + Common.Wpf + v4.0 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + ..\ChrisKaczor.pfx + + + 3.5 + + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true + Client + SAK + SAK + SAK + SAK + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + AnyCPU + AllRules.ruleset + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + AllRules.ruleset + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + bin\Debug\Common.Wpf.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + AllRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + true + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + bin\Release\Common.Wpf.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + AllRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + bin\Debug\Common.Wpf.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + AllRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + false + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + bin\Release\Common.Wpf.dll.CodeAnalysisLog.xml + true + GlobalSuppressions.cs + prompt + AllRules.ruleset + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\\Rule Sets + true + ;C:\Program Files (x86)\Microsoft Visual Studio 10.0\Team Tools\Static Analysis Tools\FxCop\\Rules + true + + + + + + 3.5 + + + + + + + + 3.0 + + + 3.0 + + + 3.0 + + + + + + + + + + + + + HtmlLabel.xaml + + + + + + + + + LinkControl.xaml + + + Code + + + + + + + TextList.xaml + + + + ImageButton.xaml + + + + + + + + Designer + MSBuild:Compile + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + true + + + False + Windows Installer 3.1 + true + + + + + {ed1c07a1-54f5-4796-8b06-2a0bb1960d84} + Common.Native + + + + + \ No newline at end of file diff --git a/ExtendedListBoxControl/ExtendedListBox.cs b/ExtendedListBoxControl/ExtendedListBox.cs new file mode 100644 index 0000000..b375be7 --- /dev/null +++ b/ExtendedListBoxControl/ExtendedListBox.cs @@ -0,0 +1,32 @@ +using System.Windows; +using System.Windows.Controls; + +namespace Common.Wpf.ExtendedListBoxControl +{ + public class ExtendedListBox : ListBox + { + protected override DependencyObject GetContainerForItemOverride() + { + return new ExtendedListBoxItem(); + } + + private int _lastSelectedIndex = -1; + + protected override void OnSelectionChanged(SelectionChangedEventArgs e) + { + base.OnSelectionChanged(e); + + if (SelectedIndex == -1 && _lastSelectedIndex != -1) + { + int itemCount = Items.Count; + + if (_lastSelectedIndex >= itemCount) + _lastSelectedIndex = itemCount - 1; + + SelectedIndex = _lastSelectedIndex; + } + + _lastSelectedIndex = SelectedIndex; + } + } +} diff --git a/ExtendedListBoxControl/ExtendedListBoxItem.cs b/ExtendedListBoxControl/ExtendedListBoxItem.cs new file mode 100644 index 0000000..cc445be --- /dev/null +++ b/ExtendedListBoxControl/ExtendedListBoxItem.cs @@ -0,0 +1,75 @@ +using System.Windows.Controls; +using System.Windows.Input; + +using Common.Wpf.Extensions; + +namespace Common.Wpf.ExtendedListBoxControl +{ + public class ExtendedListBoxItem : ListBoxItem + { + #region Helper properties + + public ExtendedListBox ParentListBox + { + get + { + return this.GetAncestor(); + } + } + + #endregion + + #region Fix selection handling for multiple selection + + private bool _fireMouseDownOnMouseUp; + private SelectionMode? _selectionMode; + + private SelectionMode SelectionMode + { + get + { + if (_selectionMode == null) + { + // Get the parent list box + ListBox listBox = ParentListBox; + + // Cache the selection mode + _selectionMode = listBox.SelectionMode; + } + + return _selectionMode.Value; + } + } + + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + // If the item is already selected we want to ignore the mouse down now and raise it on mouse up instead + if (SelectionMode != SelectionMode.Single && IsSelected) + { + _fireMouseDownOnMouseUp = true; + return; + } + + // Call the normal mouse down + base.OnMouseLeftButtonDown(e); + } + + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) + { + // If we ignored the earlier mouse down we need to fire it now + if (SelectionMode != SelectionMode.Single && _fireMouseDownOnMouseUp) + { + // Call the normal mouse down + base.OnMouseLeftButtonDown(e); + + // Clear the flag + _fireMouseDownOnMouseUp = false; + } + + // Call the normal mouse up + base.OnMouseLeftButtonUp(e); + } + + #endregion + } +} diff --git a/Extensions/ApplicationExtensions.cs b/Extensions/ApplicationExtensions.cs new file mode 100644 index 0000000..15c03e7 --- /dev/null +++ b/Extensions/ApplicationExtensions.cs @@ -0,0 +1,111 @@ +using System; +using System.IO; +using System.Reflection; +using System.Security; +using System.Security.Permissions; +using System.Runtime.InteropServices; +using System.Runtime.ConstrainedExecution; +using System.Deployment.Application; +using Microsoft.Win32; + +namespace Common.Wpf.Extensions +{ + public enum HostType + { + HostTypeDefault = 0x0, + HostTypeAppLaunch = 0x1, + HostTypeCorFlag = 0x2 + } + + // Taken from System.Windows.Forms.UnsafeNativeMethods + [StructLayout(LayoutKind.Sequential), SuppressUnmanagedCodeSecurity] + internal class ProcessInformation + { + public IntPtr hProcess = IntPtr.Zero; + public IntPtr hThread = IntPtr.Zero; + public int dwProcessId; + public int dwThreadId; + private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); + ~ProcessInformation() + { + close(); + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] + internal void close() + { + if ((hProcess != IntPtr.Zero) && (hProcess != INVALID_HANDLE_VALUE)) + { + CloseHandle(new HandleRef(this, hProcess)); + hProcess = INVALID_HANDLE_VALUE; + } + if ((hThread != IntPtr.Zero) && (hThread != INVALID_HANDLE_VALUE)) + { + CloseHandle(new HandleRef(this, hThread)); + hThread = INVALID_HANDLE_VALUE; + } + } + + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)] + private static extern bool CloseHandle(HandleRef handle); + } + + public static class ApplicationExtensions + { + [DllImport("clr.dll", CharSet = CharSet.Unicode, ExactSpelling = true, PreserveSig = false)] + internal static extern void CorLaunchApplication(uint hostType, string applicationFullName, int manifestPathsCount, string[] manifestPaths, int activationDataCount, string[] activationData, ProcessInformation processInformation); + + // Originally from System.Windows.Forms.Application, changed to suit needs + [SecurityPermission(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode), SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)] + public static void Restart(this System.Windows.Application application) + { + if (Assembly.GetEntryAssembly() == null) + { + throw new NotSupportedException("RestartNotSupported"); + } + if (ApplicationDeployment.IsNetworkDeployed) + { + string updatedApplicationFullName = ApplicationDeployment.CurrentDeployment.UpdatedApplicationFullName; + + if (System.Windows.Application.Current != null) + System.Windows.Application.Current.Shutdown(); + + CorLaunchApplication((uint) HostType.HostTypeDefault, updatedApplicationFullName, 0, null, 0, null, new ProcessInformation()); + } + } + + public static void SetStartWithWindows(this System.Windows.Application application, bool value) + { + string applicationName = Assembly.GetEntryAssembly().GetName().Name; + + SetStartWithWindows(application, applicationName, value); + } + + public static void SetStartWithWindows(this System.Windows.Application application, string applicationName, bool value) + { + string applicationPath = string.Format("\"{0}\"", Assembly.GetEntryAssembly().Location); + + SetStartWithWindows(application, applicationName, applicationPath, value); + } + + public static void SetStartWithWindows(this System.Windows.Application application, string applicationName, string applicationPath, bool value) + { + // Open the regsitry key + RegistryKey regkey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", true); + + // If we couldn't get the key then stop + if (regkey == null) + return; + + // Delete any existing key + regkey.DeleteValue(applicationName, false); + + // If auto start should not be on then we're done + if (!value) + return; + + // Set the registry key + regkey.SetValue(applicationName, applicationPath); + } + } +} diff --git a/Extensions/DependencyObjectExtensions.cs b/Extensions/DependencyObjectExtensions.cs new file mode 100644 index 0000000..0227cce --- /dev/null +++ b/Extensions/DependencyObjectExtensions.cs @@ -0,0 +1,20 @@ +using System.Windows; +using System.Windows.Media; + +namespace Common.Wpf.Extensions +{ + public static class DependencyObjectExtensions + { + public static T GetAncestor(this DependencyObject referenceObject) where T : class + { + DependencyObject parent = VisualTreeHelper.GetParent(referenceObject); + + while (parent != null && !parent.GetType().Equals(typeof(T))) + { + parent = VisualTreeHelper.GetParent(parent); + } + + return parent as T; + } + } +} diff --git a/Extensions/FontExtensions.cs b/Extensions/FontExtensions.cs new file mode 100644 index 0000000..4a38b0c --- /dev/null +++ b/Extensions/FontExtensions.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; +using System.Windows.Media; +using Microsoft.Win32; + +namespace Common.Wpf.Extensions +{ + public static class FontExtensions + { + public static bool IsComposite(FontFamily fontFamily) + { + return fontFamily.Source.StartsWith("Global"); + } + + public static bool IsSymbol(FontFamily fontFamily) + { + Typeface typeface = fontFamily.GetTypefaces().First(); + GlyphTypeface glyph; + typeface.TryGetGlyphTypeface(out glyph); + return glyph.Symbol; + } + + public static bool IsVisible(FontFamily fontFamily) + { + return !IsHidden(fontFamily); + } + + public static bool IsHidden(FontFamily fontFamily) + { + const string fontManagementKey = @"Software\Microsoft\Windows NT\CurrentVersion\Font Management"; + const string inactiveFontsValue = "Inactive Fonts"; + + RegistryKey key = Registry.CurrentUser.OpenSubKey(fontManagementKey); + + if (key == null) + return false; + + IEnumerable hiddenFonts = (string[]) key.GetValue(inactiveFontsValue); + + return hiddenFonts.Contains(fontFamily.Source); + } + } +} diff --git a/Extensions/GridExtensions.cs b/Extensions/GridExtensions.cs new file mode 100644 index 0000000..51144df --- /dev/null +++ b/Extensions/GridExtensions.cs @@ -0,0 +1,18 @@ +using System.Windows.Controls; + +namespace Common.Wpf.Extensions +{ + public static class GridExtensions + { + public static int GetRowIndex(this Grid grid, RowDefinition rowDefinition) + { + for (int i = 0; i < grid.RowDefinitions.Count; i++) + { + if (grid.RowDefinitions[i] == rowDefinition) + return i; + } + + return -1; + } + } +} diff --git a/Extensions/WindowExtensions.cs b/Extensions/WindowExtensions.cs new file mode 100644 index 0000000..812c805 --- /dev/null +++ b/Extensions/WindowExtensions.cs @@ -0,0 +1,103 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Media; + +namespace Common.Wpf.Extensions +{ + public static class WindowExtensions + { + public static Dictionary GetExplicitBindingExpressions(this DependencyObject parent) + { + // Create a dictionary of framework elements and binding expressions + Dictionary bindingExpressions = new Dictionary(); + + // Get all explict bindings into the list + GetExplicitBindingExpressions(parent, ref bindingExpressions); + + return bindingExpressions; + } + + private static void GetExplicitBindingExpressions(DependencyObject parent, ref Dictionary bindingExpressions) + { + // Get the number of children + int childCount = VisualTreeHelper.GetChildrenCount(parent); + + // Loop over each child + for (int childIndex = 0; childIndex < childCount; childIndex++) + { + // Get the child + DependencyObject dependencyObject = VisualTreeHelper.GetChild(parent, childIndex); + + // Check if the object is a tab control + if (dependencyObject is TabControl) + { + // Cast the tab control + TabControl tabControl = (dependencyObject as TabControl); + + // Loop over each tab + foreach (TabItem tabItem in tabControl.Items) + GetExplicitBindingExpressions((DependencyObject) tabItem.Content, ref bindingExpressions); + } + else + { + // See if the child is a framework element + if (dependencyObject is FrameworkElement) + { + // Cast to framework element + FrameworkElement frameworkElement = (FrameworkElement) dependencyObject; + + // Get the list of properties + IEnumerable dependencyProperties = (from PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(dependencyObject) + select DependencyPropertyDescriptor.FromProperty(propertyDescriptor) + into dependencyPropertyDescriptor + where dependencyPropertyDescriptor != null + select dependencyPropertyDescriptor.DependencyProperty).ToList(); + + // Loop over each dependency property in the list + foreach (DependencyProperty dependencyProperty in dependencyProperties) + { + // Try to get the binding expression for the property + BindingExpression bindingExpression = frameworkElement.GetBindingExpression(dependencyProperty); + + // If there is a binding expression and it is set to explicit then make it update the source + if (bindingExpression != null && bindingExpression.ParentBinding.UpdateSourceTrigger == UpdateSourceTrigger.Explicit) + bindingExpressions.Add(frameworkElement, bindingExpression); + } + } + + // If the dependency object has any children then check them + if (VisualTreeHelper.GetChildrenCount(dependencyObject) > 0) + GetExplicitBindingExpressions(dependencyObject, ref bindingExpressions); + } + } + + } + + public static void UpdateAllSources(this DependencyObject window) + { + UpdateAllSources(window, GetExplicitBindingExpressions(window).Values); + } + + public static void UpdateAllSources(this DependencyObject window, IEnumerable bindingExpressions) + { + foreach (var expression in bindingExpressions) + expression.UpdateSource(); + } + + public static void ClearAllValidationErrors(this DependencyObject window) + { + ClearAllValidationErrors(window, GetExplicitBindingExpressions(window).Values); + } + + public static void ClearAllValidationErrors(this DependencyObject window, IEnumerable bindingExpressions) + { + foreach (var expression in bindingExpressions) + Validation.ClearInvalid(expression); + } + + } +} diff --git a/HtmlLabelControl/HtmlLabel.xaml b/HtmlLabelControl/HtmlLabel.xaml new file mode 100644 index 0000000..a1ccaae --- /dev/null +++ b/HtmlLabelControl/HtmlLabel.xaml @@ -0,0 +1,7 @@ + + + + + diff --git a/HtmlLabelControl/HtmlLabel.xaml.cs b/HtmlLabelControl/HtmlLabel.xaml.cs new file mode 100644 index 0000000..771eed2 --- /dev/null +++ b/HtmlLabelControl/HtmlLabel.xaml.cs @@ -0,0 +1,122 @@ +using System.Collections.ObjectModel; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace Common.Wpf.HtmlLabelControl +{ + /// + /// Interaction logic for HtmlLabel.xaml + /// + public partial class HtmlLabel : UserControl + { + private Collection _textLines; + + public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(HtmlLabel), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsRender)); + + public HtmlLabel() + { + InitializeComponent(); + } + + protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + base.OnPropertyChanged(e); + + switch (e.Property.Name) + { + case "FontFamily": + case "FontSize": + case "Foreground": + // Force the control to re-parse + Text = Text; + break; + + case "HorizontalContentAlignment": + case "VerticalContentAlignment": + case "Padding": + InvalidateVisual(); + break; + } + } + + protected override void OnRender(DrawingContext drawingContext) + { + if (string.IsNullOrEmpty(Text)) + return; + + Point drawPoint = new Point(Padding.Left, Padding.Top); + + switch (VerticalContentAlignment) + { + case VerticalAlignment.Bottom: + drawPoint.Y = ActualHeight - Padding.Bottom; + + foreach (TextLine line in _textLines) + { + drawPoint.Y -= line.Height; + } + + break; + + case VerticalAlignment.Center: + double totalHeight = 0; + + foreach (TextLine line in _textLines) + { + totalHeight += line.Height; + } + + drawPoint.Y = ((ActualHeight - (Padding.Top + Padding.Bottom)) / 2) - (totalHeight / 2); + + break; + } + + foreach (TextLine line in _textLines) + { + if (drawPoint.Y < 0) + { + drawPoint.Y += line.Height; + continue; + } + + drawPoint.X = Padding.Left; + + switch (HorizontalContentAlignment) + { + case HorizontalAlignment.Right: + drawPoint.X = ActualWidth - line.Width - Padding.Right; + break; + + case HorizontalAlignment.Center: + drawPoint.X = ((ActualWidth - (Padding.Left + Padding.Right)) / 2) - (line.Width / 2); + break; + } + + foreach (TextFragment fragment in line.FragmentList) + { + drawingContext.DrawText(fragment.FormattedText, drawPoint); + + drawPoint.X += fragment.FormattedText.WidthIncludingTrailingWhitespace; + } + + drawPoint.Y += line.Height; + } + } + + public string Text + { + get + { + return (string) GetValue(TextProperty); + } + set + { + TextParser textParser = new TextParser(); + _textLines = textParser.Parse(this, value); + + SetValue(TextProperty, value); + } + } + } +} \ No newline at end of file diff --git a/HtmlLabelControl/TextFragment.cs b/HtmlLabelControl/TextFragment.cs new file mode 100644 index 0000000..b4f5ded --- /dev/null +++ b/HtmlLabelControl/TextFragment.cs @@ -0,0 +1,63 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Media; + +namespace Common.Wpf.HtmlLabelControl +{ + public class TextFragment + { + private readonly HtmlLabel _parent; + + public Brush Color { get; set; } + public FontStyle Style { get; set; } + public FontWeight Weight { get; set; } + public double Size { get; set; } + public string Text { get; set; } + public bool Underline { get; set; } + + private Typeface _typeface; + public Typeface Typeface + { + get { return _typeface ?? (_typeface = new Typeface(_parent.FontFamily, Style, Weight, _parent.FontStretch)); } + } + + private FormattedText _formattedText; + public FormattedText FormattedText + { + get + { + if (_formattedText == null) + { + string measureText = (string.IsNullOrEmpty(Text) ? " " : Text); + + _formattedText = new FormattedText(measureText, CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, Typeface, Size, Color); + + TextDecorationCollection textDecorationCollection = new TextDecorationCollection(); + + if (Underline) + { + TextDecoration underlineDecoration = new TextDecoration { PenThicknessUnit = TextDecorationUnit.FontRecommended }; + + textDecorationCollection.Add(underlineDecoration); + } + + _formattedText.SetTextDecorations(textDecorationCollection); + } + + return _formattedText; + } + } + + public TextFragment(HtmlLabel parent) + { + _parent = parent; + + Color = _parent.Foreground; + Style = _parent.FontStyle; + Weight = _parent.FontWeight; + Size = _parent.FontSize; + Text = string.Empty; + Underline = false; + } + } +} \ No newline at end of file diff --git a/HtmlLabelControl/TextFragmentStyle.cs b/HtmlLabelControl/TextFragmentStyle.cs new file mode 100644 index 0000000..228e41f --- /dev/null +++ b/HtmlLabelControl/TextFragmentStyle.cs @@ -0,0 +1,32 @@ +using System.Windows; +using System.Windows.Media; + +namespace Common.Wpf.HtmlLabelControl +{ + public class TextFragmentStyle + { + public Brush Color { get; set; } + public FontStyle? Style { get; set; } + public FontWeight? Weight { get; set; } + public double? Size { get; set; } + public bool? Underline { get; set; } + + public void Apply(TextFragment fragment) + { + if (Color != null) + fragment.Color = Color; + + if (Style.HasValue) + fragment.Style = Style.Value; + + if (Weight.HasValue) + fragment.Weight = Weight.Value; + + if (Size.HasValue) + fragment.Size = Size.Value; + + if (Underline.HasValue) + fragment.Underline = Underline.Value; + } + } +} \ No newline at end of file diff --git a/HtmlLabelControl/TextLine.cs b/HtmlLabelControl/TextLine.cs new file mode 100644 index 0000000..db32568 --- /dev/null +++ b/HtmlLabelControl/TextLine.cs @@ -0,0 +1,49 @@ +using System.Collections.ObjectModel; + +namespace Common.Wpf.HtmlLabelControl +{ + public class TextLine + { + private double _height; + public double Height + { + get + { + if (_height == 0) + { + foreach (TextFragment textFragment in FragmentList) + { + if (textFragment.FormattedText.Height > _height) + _height = textFragment.FormattedText.Height; + } + } + + return _height; + } + } + + private double _width; + public double Width + { + get + { + if (_width == 0) + { + foreach (TextFragment textFragment in FragmentList) + { + _width += textFragment.FormattedText.Width; + } + } + + return _width; + } + } + + public Collection FragmentList { get; private set; } + + public TextLine() + { + FragmentList = new Collection(); + } + } +} diff --git a/HtmlLabelControl/TextParser.cs b/HtmlLabelControl/TextParser.cs new file mode 100644 index 0000000..2e64cb2 --- /dev/null +++ b/HtmlLabelControl/TextParser.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Windows; +using System.Windows.Media; +using System.Xml; + +namespace Common.Wpf.HtmlLabelControl +{ + public class TextParser + { + private HtmlLabel _parentControl; + + public Collection Parse(HtmlLabel parentControl, string text) + { + _parentControl = parentControl; + + // Add a root tag so the parser is happy + text = string.Format(CultureInfo.InvariantCulture, "{0}", text); + + // Normalize line endings + text = text.Replace("\r\n", "\n"); + + // Create an XML document and load it with the text + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.PreserveWhitespace = true; + xmlDocument.LoadXml(text); + + // Create a list of text lines + Collection lines = new Collection(); + + // Walk over the nodes and build up the fragment list + walkNodes(xmlDocument.ChildNodes, lines); + + return lines; + } + + private readonly Stack _attributeStack = new Stack(); + + private void walkNodes(XmlNodeList xmlNodeList, Collection textLines) + { + if (textLines.Count == 0) + textLines.Add(new TextLine()); + + foreach (XmlNode xmlNode in xmlNodeList) + { + TextFragmentStyle style; + + switch (xmlNode.Name.ToUpperInvariant()) + { + case "#WHITESPACE": + case "#TEXT": + + // Split the fragment and the line endings + string[] lines = xmlNode.Value.Split('\n'); + + bool firstLine = true; + + foreach (string line in lines) + { + TextLine textLine = (firstLine ? textLines[textLines.Count - 1] : new TextLine()); + + // Create a new fragment and fill the style information + TextFragment textFragment = new TextFragment(_parentControl); + textFragment.Text = line; + + foreach (TextFragmentStyle s in _attributeStack) + { + s.Apply(textFragment); + } + + // Add the fragment to the list + textLine.FragmentList.Add(textFragment); + + if (!firstLine) + textLines.Add(textLine); + + firstLine = false; + } + + break; + + case "B": + + style = new TextFragmentStyle { Weight = FontWeights.Bold }; + _attributeStack.Push(style); + + break; + + case "U": + + style = new TextFragmentStyle { Underline = true }; + _attributeStack.Push(style); + + break; + + case "CITE": + + style = new TextFragmentStyle { Style = FontStyles.Italic }; + _attributeStack.Push(style); + + break; + + case "FONT": + style = new TextFragmentStyle(); + + foreach (XmlAttribute attribute in xmlNode.Attributes) + { + switch (attribute.Name.ToUpperInvariant()) + { + case "SIZE": + style.Size = Convert.ToDouble(attribute.Value, CultureInfo.InvariantCulture); + break; + + case "COLOR": + style.Color = (Brush) new BrushConverter().ConvertFromString(attribute.Value); + break; + } + } + + _attributeStack.Push(style); + break; + } + + if (xmlNode.ChildNodes.Count > 0) + walkNodes(xmlNode.ChildNodes, textLines); + + if (_attributeStack.Count > 0) + _attributeStack.Pop(); + } + } + } +} \ No newline at end of file diff --git a/HtmlTextBlock/HtmlTextBlock.cs b/HtmlTextBlock/HtmlTextBlock.cs new file mode 100644 index 0000000..0162254 --- /dev/null +++ b/HtmlTextBlock/HtmlTextBlock.cs @@ -0,0 +1,57 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; + +namespace Common.Wpf.HtmlTextBlock +{ + public class HtmlTextBlock : TextBlock + { + public static readonly DependencyProperty HtmlProperty = DependencyProperty.Register("Html", typeof(string), typeof(HtmlTextBlock), new UIPropertyMetadata("Html", OnHtmlChanged)); + + public static void OnHtmlChanged(DependencyObject s, DependencyPropertyChangedEventArgs e) + { + HtmlTextBlock htmlTextBlock = (HtmlTextBlock) s; + + Parse(htmlTextBlock, e.NewValue as string); + } + + private static void Parse(HtmlTextBlock control, string value) + { + try + { + control.Inlines.Clear(); + + TextParser parser = new TextParser(); + var lines = parser.Parse(control, value); + + foreach (TextLine line in lines) + { + foreach (TextFragment fragment in line.FragmentList) + { + Run run = new Run(fragment.Text); + + run.FontStyle = fragment.Style; + run.FontWeight = fragment.Weight; + run.FontSize = fragment.Size; + run.Foreground = fragment.Color; + + control.Inlines.Add(run); + } + } + } + catch (Exception) + { + control.Inlines.Clear(); + + control.Text = value; + } + } + + public string Html + { + get { return (string) GetValue(HtmlProperty); } + set { SetValue(HtmlProperty, value); } + } + } +} diff --git a/HtmlTextBlock/TextFragment.cs b/HtmlTextBlock/TextFragment.cs new file mode 100644 index 0000000..d956181 --- /dev/null +++ b/HtmlTextBlock/TextFragment.cs @@ -0,0 +1,63 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Media; + +namespace Common.Wpf.HtmlTextBlock +{ + public class TextFragment + { + private readonly HtmlTextBlock _parent; + + public Brush Color { get; set; } + public FontStyle Style { get; set; } + public FontWeight Weight { get; set; } + public double Size { get; set; } + public string Text { get; set; } + public bool Underline { get; set; } + + private Typeface _typeface; + public Typeface Typeface + { + get { return _typeface ?? (_typeface = new Typeface(_parent.FontFamily, Style, Weight, _parent.FontStretch)); } + } + + private FormattedText _formattedText; + public FormattedText FormattedText + { + get + { + if (_formattedText == null) + { + string measureText = (string.IsNullOrEmpty(Text) ? " " : Text); + + _formattedText = new FormattedText(measureText, CultureInfo.CurrentUICulture, FlowDirection.LeftToRight, Typeface, Size, Color); + + TextDecorationCollection textDecorationCollection = new TextDecorationCollection(); + + if (Underline) + { + TextDecoration underlineDecoration = new TextDecoration { PenThicknessUnit = TextDecorationUnit.FontRecommended }; + + textDecorationCollection.Add(underlineDecoration); + } + + _formattedText.SetTextDecorations(textDecorationCollection); + } + + return _formattedText; + } + } + + public TextFragment(HtmlTextBlock parent) + { + _parent = parent; + + Color = _parent.Foreground; + Style = _parent.FontStyle; + Weight = _parent.FontWeight; + Size = _parent.FontSize; + Text = string.Empty; + Underline = false; + } + } +} \ No newline at end of file diff --git a/HtmlTextBlock/TextFragmentStyle.cs b/HtmlTextBlock/TextFragmentStyle.cs new file mode 100644 index 0000000..d674de6 --- /dev/null +++ b/HtmlTextBlock/TextFragmentStyle.cs @@ -0,0 +1,32 @@ +using System.Windows; +using System.Windows.Media; + +namespace Common.Wpf.HtmlTextBlock +{ + public class TextFragmentStyle + { + public Brush Color { get; set; } + public FontStyle? Style { get; set; } + public FontWeight? Weight { get; set; } + public double? Size { get; set; } + public bool? Underline { get; set; } + + public void Apply(TextFragment fragment) + { + if (Color != null) + fragment.Color = Color; + + if (Style.HasValue) + fragment.Style = Style.Value; + + if (Weight.HasValue) + fragment.Weight = Weight.Value; + + if (Size.HasValue) + fragment.Size = Size.Value; + + if (Underline.HasValue) + fragment.Underline = Underline.Value; + } + } +} \ No newline at end of file diff --git a/HtmlTextBlock/TextLine.cs b/HtmlTextBlock/TextLine.cs new file mode 100644 index 0000000..9ebb350 --- /dev/null +++ b/HtmlTextBlock/TextLine.cs @@ -0,0 +1,14 @@ +using System.Collections.ObjectModel; + +namespace Common.Wpf.HtmlTextBlock +{ + public class TextLine + { + public Collection FragmentList { get; private set; } + + public TextLine() + { + FragmentList = new Collection(); + } + } +} diff --git a/HtmlTextBlock/TextParser.cs b/HtmlTextBlock/TextParser.cs new file mode 100644 index 0000000..19a713e --- /dev/null +++ b/HtmlTextBlock/TextParser.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Windows; +using System.Windows.Media; +using System.Xml; + +namespace Common.Wpf.HtmlTextBlock +{ + public class TextParser + { + private HtmlTextBlock _parentControl; + + public Collection Parse(HtmlTextBlock parentControl, string text) + { + _parentControl = parentControl; + + text = text.Replace("&", "&"); + + // Add a root tag so the parser is happy + text = string.Format(CultureInfo.InvariantCulture, "{0}", text); + + // Normalize line endings + text = text.Replace("\r\n", "\n"); + + // Create an XML document and load it with the text + XmlDocument xmlDocument = new XmlDocument(); + xmlDocument.PreserveWhitespace = true; + xmlDocument.LoadXml(text); + + // Create a list of text lines + Collection lines = new Collection(); + + // Walk over the nodes and build up the fragment list + WalkNodes(xmlDocument.ChildNodes, lines); + + return lines; + } + + private readonly Stack _attributeStack = new Stack(); + + private void WalkNodes(XmlNodeList xmlNodeList, Collection textLines) + { + if (textLines.Count == 0) + textLines.Add(new TextLine()); + + foreach (XmlNode xmlNode in xmlNodeList) + { + TextFragmentStyle style; + + switch (xmlNode.Name.ToUpperInvariant()) + { + case "#WHITESPACE": + case "#TEXT": + + // Split the fragment and the line endings + string[] lines = xmlNode.Value.Split('\n'); + + bool firstLine = true; + + foreach (string line in lines) + { + TextLine textLine = (firstLine ? textLines[textLines.Count - 1] : new TextLine()); + + // Create a new fragment and fill the style information + TextFragment textFragment = new TextFragment(_parentControl); + textFragment.Text = line; + + foreach (TextFragmentStyle s in _attributeStack) + { + s.Apply(textFragment); + } + + // Add the fragment to the list + textLine.FragmentList.Add(textFragment); + + if (!firstLine) + textLines.Add(textLine); + + firstLine = false; + } + + break; + + case "EM": + case "B": + + style = new TextFragmentStyle { Weight = FontWeights.Bold }; + _attributeStack.Push(style); + + break; + + case "U": + + style = new TextFragmentStyle { Underline = true }; + _attributeStack.Push(style); + + break; + + case "CITE": + + style = new TextFragmentStyle { Style = FontStyles.Italic }; + _attributeStack.Push(style); + + break; + + case "FONT": + style = new TextFragmentStyle(); + + if (xmlNode.Attributes == null) + break; + + foreach (XmlAttribute attribute in xmlNode.Attributes) + { + switch (attribute.Name.ToUpperInvariant()) + { + case "SIZE": + style.Size = Convert.ToDouble(attribute.Value, CultureInfo.InvariantCulture); + break; + + case "COLOR": + style.Color = (Brush) new BrushConverter().ConvertFromString(attribute.Value); + break; + } + } + + _attributeStack.Push(style); + break; + } + + if (xmlNode.ChildNodes.Count > 0) + WalkNodes(xmlNode.ChildNodes, textLines); + + if (_attributeStack.Count > 0) + _attributeStack.Pop(); + } + } + } +} \ No newline at end of file diff --git a/LinkControl/LinkControl.xaml b/LinkControl/LinkControl.xaml new file mode 100644 index 0000000..35b6372 --- /dev/null +++ b/LinkControl/LinkControl.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/LinkControl/LinkControl.xaml.cs b/LinkControl/LinkControl.xaml.cs new file mode 100644 index 0000000..fa85463 --- /dev/null +++ b/LinkControl/LinkControl.xaml.cs @@ -0,0 +1,38 @@ +using System.Windows; + +namespace Common.Wpf.LinkControl +{ + public partial class LinkControl + { + public event RoutedEventHandler Click; + + public LinkControl() + { + InitializeComponent(); + + HyperlinkControl.Click += HandleHyperlinkControlClick; + } + + private void HandleHyperlinkControlClick(object sender, RoutedEventArgs e) + { + if (Click != null) + Click.Invoke(sender, e); + } + + public string Text + { + get { return ContentControl.Text; } + set { ContentControl.Text = value; } + } + + public new bool IsEnabled + { + get { return base.IsEnabled; } + set + { + base.IsEnabled = value; + HyperlinkControl.IsEnabled = value; + } + } + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..944319b --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,58 @@ +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Common.Wpf")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Common.Wpf")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2009")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: CLSCompliant(true)] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TextListControl/TextList.xaml b/TextListControl/TextList.xaml new file mode 100644 index 0000000..93fefc2 --- /dev/null +++ b/TextListControl/TextList.xaml @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/TextListControl/TextList.xaml.cs b/TextListControl/TextList.xaml.cs new file mode 100644 index 0000000..cc0c4da --- /dev/null +++ b/TextListControl/TextList.xaml.cs @@ -0,0 +1,95 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace Common.Wpf.TextListControl +{ + public partial class TextList : ListBox + { + #region Events + + public static RoutedEvent ListItemMouseUpEvent = EventManager.RegisterRoutedEvent("ListItemMouseUp", RoutingStrategy.Bubble, typeof(MouseButtonEventArgs), typeof(TextList)); + public event MouseButtonEventHandler ListItemMouseUp; + + public void OnListItemMouseUp(object listBoxItem, MouseButtonEventArgs e) + { + MouseButtonEventHandler handler = ListItemMouseUp; + if (handler != null) handler(listBoxItem, e); + } + + public static RoutedEvent ListItemMouseDoubleClickEvent = EventManager.RegisterRoutedEvent("ListItemMouseDoubleClick", RoutingStrategy.Bubble, typeof(MouseButtonEventArgs), typeof(TextList)); + public event MouseButtonEventHandler ListItemMouseDoubleClick; + + public void OnListItemMouseDoubleClick(object listBoxItem, MouseButtonEventArgs e) + { + MouseButtonEventHandler handler = ListItemMouseDoubleClick; + if (handler != null) handler(listBoxItem, e); + } + + #endregion + + #region Constructor + + public TextList() + { + InitializeComponent(); + } + + #endregion + + #region Hover selection events + + private void handleListItemMouseEnter(object sender, MouseEventArgs e) + { + // Make sure the control has focus + Focus(); + + // Get the list box item + ListBoxItem listBoxItem = (ListBoxItem) sender; + + // Select the data context + SelectedItem = listBoxItem.DataContext; + + // Set the cursor + listBoxItem.Cursor = Cursors.Hand; + } + + private void handleListItemMouseLeave(object sender, MouseEventArgs e) + { + // Clear selection + SelectedItem = null; + } + + #endregion + + #region List item events + + private void handleListItemMouseUp(object sender, MouseButtonEventArgs mouseButtonEventArgs) + { + // Get the list box item + ListBoxItem listBoxItem = (ListBoxItem) sender; + + // Build the event args + MouseButtonEventArgs eventArgs = new MouseButtonEventArgs(mouseButtonEventArgs.MouseDevice, mouseButtonEventArgs.Timestamp, mouseButtonEventArgs.ChangedButton); + eventArgs.RoutedEvent = ListItemMouseUpEvent; + + // Raise the event + OnListItemMouseUp(listBoxItem, eventArgs); + } + + private void handleListItemMouseDoubleClick(object sender, MouseButtonEventArgs mouseButtonEventArgs) + { + // Get the list box item + ListBoxItem listBoxItem = (ListBoxItem) sender; + + // Build the event args + MouseButtonEventArgs eventArgs = new MouseButtonEventArgs(mouseButtonEventArgs.MouseDevice, mouseButtonEventArgs.Timestamp, mouseButtonEventArgs.ChangedButton); + eventArgs.RoutedEvent = ListItemMouseDoubleClickEvent; + + // Raise the event + OnListItemMouseDoubleClick(listBoxItem, eventArgs); + } + + #endregion + } +} diff --git a/Toolbar/DropDownButton/DropDownButton.cs b/Toolbar/DropDownButton/DropDownButton.cs new file mode 100644 index 0000000..84aff17 --- /dev/null +++ b/Toolbar/DropDownButton/DropDownButton.cs @@ -0,0 +1,97 @@ +// -------------------------------- +// Copyright (c) Huy Pham. All rights reserved. +// This source code is made available under the terms of the Microsoft Public License (Ms-PL) +// http://www.opensource.org/licenses/ms-pl.html +// --------------------------------- + +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Media; + +namespace Common.Wpf.Toolbar.DropDownButton +{ + public class DropDownButton : ToggleButton + { + #region Dependency Properties + + public static readonly DependencyProperty DropDownContextMenuProperty = DependencyProperty.Register("DropDownContextMenu", typeof(ContextMenu), typeof(DropDownButton), new UIPropertyMetadata(null)); + public static readonly DependencyProperty ImageProperty = DependencyProperty.Register("Image", typeof(ImageSource), typeof(DropDownButton)); + public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(DropDownButton)); + public static readonly DependencyProperty TargetProperty = DependencyProperty.Register("Target", typeof(UIElement), typeof(DropDownButton)); + public static readonly DependencyProperty DropDownButtonCommandProperty = DependencyProperty.Register("DropDownButtonCommand", typeof(ICommand), typeof(DropDownButton), new FrameworkPropertyMetadata(null)); + + #endregion + + #region Constructors + + public DropDownButton() + { + // Bind the ToogleButton.IsChecked property to the drop-down's IsOpen property + var binding = new Binding("DropDownContextMenu.IsOpen") {Source = this}; + SetBinding(IsCheckedProperty, binding); + } + + #endregion + + #region Properties + + public ContextMenu DropDownContextMenu + { + get { return GetValue(DropDownContextMenuProperty) as ContextMenu; } + set { SetValue(DropDownContextMenuProperty, value); } + } + + public ImageSource Image + { + get { return GetValue(ImageProperty) as ImageSource; } + set { SetValue(ImageProperty, value); } + } + + public string Text + { + get { return GetValue(TextProperty) as string; } + set { SetValue(TextProperty, value); } + } + + public UIElement Target + { + get { return GetValue(TargetProperty) as UIElement; } + set { SetValue(TargetProperty, value); } + } + + public ICommand DropDownButtonCommand + { + get { return GetValue(DropDownButtonCommandProperty) as ICommand; } + set { SetValue(DropDownButtonCommandProperty, value); } + } + + #endregion + + #region Protected Override Methods + + protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + base.OnPropertyChanged(e); + + if (e.Property == DropDownButtonCommandProperty) + Command = DropDownButtonCommand; + } + + protected override void OnClick() + { + if (DropDownContextMenu == null) return; + + if (DropDownButtonCommand != null) DropDownButtonCommand.Execute(null); + + // If there is a drop-down assigned to this button, then position and display it + DropDownContextMenu.PlacementTarget = this; + DropDownContextMenu.Placement = PlacementMode.Bottom; + DropDownContextMenu.IsOpen = !DropDownContextMenu.IsOpen; + } + + #endregion + } +} \ No newline at end of file diff --git a/Toolbar/DropDownButton/DropDownButtonStyle.xaml b/Toolbar/DropDownButton/DropDownButtonStyle.xaml new file mode 100644 index 0000000..5406014 --- /dev/null +++ b/Toolbar/DropDownButton/DropDownButtonStyle.xaml @@ -0,0 +1,226 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Toolbar/ImageButton.xaml b/Toolbar/ImageButton.xaml new file mode 100644 index 0000000..c15145d --- /dev/null +++ b/Toolbar/ImageButton.xaml @@ -0,0 +1,25 @@ + diff --git a/Toolbar/ImageButton.xaml.cs b/Toolbar/ImageButton.xaml.cs new file mode 100644 index 0000000..9d295d1 --- /dev/null +++ b/Toolbar/ImageButton.xaml.cs @@ -0,0 +1,30 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace Common.Wpf.Toolbar +{ + public partial class ImageButton + { + public ImageButton() + { + InitializeComponent(); + + Loaded += HandleImageButtonLoaded; + } + + void HandleImageButtonLoaded(object sender, RoutedEventArgs e) + { + if (Style == null && Parent is ToolBar) + { + Style = (Style) FindResource(ToolBar.ButtonStyleKey); + } + } + + public ImageSource ImageSource + { + get { return image.Source; } + set { image.Source = value; } + } + } +} diff --git a/Toolbar/SplitButton/SplitButton.cs b/Toolbar/SplitButton/SplitButton.cs new file mode 100644 index 0000000..3d872e6 --- /dev/null +++ b/Toolbar/SplitButton/SplitButton.cs @@ -0,0 +1,150 @@ +// -------------------------------- +// Copyright (c) Huy Pham. All rights reserved. +// This source code is made available under the terms of the Microsoft Public License (Ms-PL) +// http://www.opensource.org/licenses/ms-pl.html +// --------------------------------- + +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Media; + +namespace Common.Wpf.Toolbar.SplitButton +{ + [TemplatePart(Name = "PART_Button", Type = typeof(ButtonBase))] + public class SplitButton : ToggleButton + { + #region Dependency Properties + + public static readonly DependencyProperty DropDownContextMenuProperty = DependencyProperty.Register("DropDownContextMenu", typeof(ContextMenu), typeof(SplitButton), new UIPropertyMetadata(null)); + public static readonly DependencyProperty ImageProperty = DependencyProperty.Register("Image", typeof(ImageSource), typeof(SplitButton)); + public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(SplitButton)); + public static readonly DependencyProperty TargetProperty = DependencyProperty.Register("Target", typeof(UIElement), typeof(SplitButton)); + public static readonly DependencyProperty MainButtonCommandProperty = DependencyProperty.Register("MainButtonCommand", typeof(ICommand), typeof(SplitButton), new FrameworkPropertyMetadata(null)); + public static readonly DependencyProperty DropDownButtonCommandProperty = DependencyProperty.Register("DropDownButtonCommand", typeof(ICommand), typeof(SplitButton), new FrameworkPropertyMetadata(null)); + + #endregion + + #region Constructors + + public SplitButton() + { + // Bind the ToogleButton.IsChecked property to the drop-down's IsOpen property + var binding = new Binding("DropDownContextMenu.IsOpen") { Source = this }; + SetBinding(IsCheckedProperty, binding); + + Loaded += HandleSplitButtonLoaded; + } + + void HandleSplitButtonLoaded(object sender, RoutedEventArgs e) + { + if (Parent is ToolBar) + { + Style style = (Style) TryFindResource("ToolBarSplitButtonStyle"); + if (style != null) + Style = style; + } + + Loaded -= HandleSplitButtonLoaded; + } + + #endregion + + #region Properties + + public ContextMenu DropDownContextMenu + { + get { return GetValue(DropDownContextMenuProperty) as ContextMenu; } + set { SetValue(DropDownContextMenuProperty, value); } + } + + public ImageSource Image + { + get { return GetValue(ImageProperty) as ImageSource; } + set { SetValue(ImageProperty, value); } + } + + public string Text + { + get { return GetValue(TextProperty) as string; } + set { SetValue(TextProperty, value); } + } + + public UIElement Target + { + get { return GetValue(TargetProperty) as UIElement; } + set { SetValue(TargetProperty, value); } + } + + public ICommand MainButtonCommand + { + get { return GetValue(MainButtonCommandProperty) as ICommand; } + set { SetValue(MainButtonCommandProperty, value); } + } + + public ICommand DropDownButtonCommand + { + get { return GetValue(DropDownButtonCommandProperty) as ICommand; } + set { SetValue(DropDownButtonCommandProperty, value); } + } + + #endregion + + #region Public Override Methods + + /// + /// + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + SetMainButtonCommand(); + } + + #endregion + + #region Protected Override Methods + + protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + base.OnPropertyChanged(e); + + if (e.Property == MainButtonCommandProperty) + SetMainButtonCommand(); + + if (e.Property == DropDownButtonCommandProperty) + Command = DropDownButtonCommand; + } + + protected override void OnClick() + { + if (DropDownContextMenu == null) return; + + if (DropDownButtonCommand != null) + DropDownButtonCommand.Execute(null); + + // If there is a drop-down assigned to this button, then position and display it + DropDownContextMenu.PlacementTarget = this; + DropDownContextMenu.Placement = PlacementMode.Bottom; + DropDownContextMenu.IsOpen = !DropDownContextMenu.IsOpen; + } + + #endregion + + #region Private Methods + + private void SetMainButtonCommand() + { + // Set up the event handlers + if (Template != null) + { + var button = Template.FindName("PART_Button", this) as ButtonBase; + if (button != null) button.Command = MainButtonCommand; + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Toolbar/SplitButton/SplitButtonStyle.xaml b/Toolbar/SplitButton/SplitButtonStyle.xaml new file mode 100644 index 0000000..c235f18 --- /dev/null +++ b/Toolbar/SplitButton/SplitButtonStyle.xaml @@ -0,0 +1,277 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Windows/ControlBox.cs b/Windows/ControlBox.cs new file mode 100644 index 0000000..544b982 --- /dev/null +++ b/Windows/ControlBox.cs @@ -0,0 +1,178 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Interop; + +namespace Common.Wpf.Windows +{ + public static class ControlBox + { + public static readonly DependencyProperty HasHelpButtonProperty = DependencyProperty.RegisterAttached( + "HasHelpButton", + typeof(bool), + typeof(ControlBox), + new UIPropertyMetadata(false, OnControlBoxChanged)); + + public static readonly DependencyProperty HasMaximizeButtonProperty = DependencyProperty.RegisterAttached( + "HasMaximizeButton", + typeof(bool), + typeof(ControlBox), + new UIPropertyMetadata(true, OnControlBoxChanged)); + + public static readonly DependencyProperty HasMinimizeButtonProperty = DependencyProperty.RegisterAttached( + "HasMinimizeButton", + typeof(bool), + typeof(ControlBox), + new UIPropertyMetadata(true, OnControlBoxChanged)); + + public static readonly DependencyProperty HasSystemMenuProperty = DependencyProperty.RegisterAttached( + "HasSystemMenu", + typeof(bool), + typeof(ControlBox), + new UIPropertyMetadata(true, OnControlBoxChanged)); + + private const int Style = -16; + private const int ExtStyle = -20; + + private const int MaximizeBox = 0x10000; + private const int MinimizeBox = 0x20000; + private const int ContextHelp = 0x400; + private const int SystemMenu = 0x00080000; + + [AttachedPropertyBrowsableForType(typeof(Window))] + public static bool GetHasHelpButton(Window element) + { + return (bool) element.GetValue(HasHelpButtonProperty); + } + + [AttachedPropertyBrowsableForType(typeof(Window))] + public static void SetHasHelpButton(Window element, bool value) + { + element.SetValue(HasHelpButtonProperty, value); + } + + [AttachedPropertyBrowsableForType(typeof(Window))] + public static bool GetHasMaximizeButton(Window element) + { + return (bool) element.GetValue(HasMaximizeButtonProperty); + } + + [AttachedPropertyBrowsableForType(typeof(Window))] + public static void SetHasMaximizeButton(Window element, bool value) + { + element.SetValue(HasMaximizeButtonProperty, value); + } + + [AttachedPropertyBrowsableForType(typeof(Window))] + public static bool GetHasMinimizeButton(Window element) + { + return (bool) element.GetValue(HasMinimizeButtonProperty); + } + + [AttachedPropertyBrowsableForType(typeof(Window))] + public static void SetHasMinimizeButton(Window element, bool value) + { + element.SetValue(HasMinimizeButtonProperty, value); + } + + [AttachedPropertyBrowsableForType(typeof(Window))] + public static bool GetHasSystemMenu(Window element) + { + return (bool) element.GetValue(HasSystemMenuProperty); + } + + [AttachedPropertyBrowsableForType(typeof(Window))] + public static void SetHasSystemMenu(Window element, bool value) + { + element.SetValue(HasSystemMenuProperty, value); + } + + private static void OnControlBoxChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var window = d as Window; + if (window != null) + { + var hWnd = new WindowInteropHelper(window).Handle; + if (hWnd == IntPtr.Zero) + { + window.SourceInitialized += OnWindowSourceInitialized; + } + else + { + UpdateStyle(window, hWnd); + UpdateExtendedStyle(window, hWnd); + } + } + } + + private static void OnWindowSourceInitialized(object sender, EventArgs e) + { + var window = (Window) sender; + + var hWnd = new WindowInteropHelper(window).Handle; + UpdateStyle(window, hWnd); + UpdateExtendedStyle(window, hWnd); + + window.SourceInitialized -= OnWindowSourceInitialized; + } + + private static void UpdateStyle(Window window, IntPtr hWnd) + { + var style = NativeMethods.GetWindowLong(hWnd, Style); + + if (GetHasMaximizeButton(window)) + { + style |= MaximizeBox; + } + else + { + style &= ~MaximizeBox; + } + + if (GetHasMinimizeButton(window)) + { + style |= MinimizeBox; + } + else + { + style &= ~MinimizeBox; + } + + if (GetHasSystemMenu(window)) + { + style |= SystemMenu; + } + else + { + style &= ~SystemMenu; + } + + NativeMethods.SetWindowLong(hWnd, Style, style); + } + + private static void UpdateExtendedStyle(Window window, IntPtr hWnd) + { + var style = NativeMethods.GetWindowLong(hWnd, ExtStyle); + + if (GetHasHelpButton(window)) + { + style |= ContextHelp; + } + else + { + style &= -~ContextHelp; + } + + NativeMethods.SetWindowLong(hWnd, ExtStyle, style); + } + + private static class NativeMethods + { + [DllImport("user32.dll")] + internal static extern int GetWindowLong(IntPtr hWnd, int index); + + [DllImport("user32.dll")] + internal static extern int SetWindowLong(IntPtr hWnd, int index, int newLong); + } + } +} \ No newline at end of file diff --git a/Windows/SnappingWindow.cs b/Windows/SnappingWindow.cs new file mode 100644 index 0000000..3956d93 --- /dev/null +++ b/Windows/SnappingWindow.cs @@ -0,0 +1,324 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Interop; + +using Common.Native; + +using Rectangle = System.Drawing.Rectangle; +using Screen = System.Windows.Forms.Screen; + +namespace Common.Wpf.Windows +{ + public class SnappingWindow : Window + { + #region Member variables + + private HwndSource _hwndSource; + private Structures.WindowPosition _lastWindowPosition; + + #endregion + + #region Enumerations + + private enum SnapMode + { + Move, + Resize + } + + #endregion + + #region Properties + + protected virtual int SnapDistance + { + get { return 20; } + } + + protected virtual List OtherWindows + { + get { return null; } + } + + #endregion + + #region Window overrides + + protected override void OnSourceInitialized(EventArgs e) + { + base.OnSourceInitialized(e); + + // Store the window handle + _hwndSource = PresentationSource.FromVisual(this) as HwndSource; + + // If we failed to get the hwnd then don't bother + if (_hwndSource == null) + return; + + // Add a hook + _hwndSource.AddHook(WndProc); + } + + protected override void OnContentRendered(EventArgs e) + { + base.OnContentRendered(e); + + // Initialize the last window position + _lastWindowPosition = new Structures.WindowPosition + { + Left = (int) Left, + Width = (int) Width, + Height = (int) Height, + Top = (int) Top + }; + } + + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + + // Unhook the window procedure + _hwndSource.RemoveHook(WndProc); + _hwndSource.Dispose(); + } + + #endregion + + #region Window procedure + + private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) + { + if (msg == (int) Constants.WindowMessage.WindowPositionChanging) + return OnWindowPositionChanging(lParam, ref handled); + + return IntPtr.Zero; + } + + #endregion + + #region Snapping + + private IntPtr OnWindowPositionChanging(IntPtr lParam, ref bool handled) + { + int snapDistance = SnapDistance; + + // Initialize whether we've updated the position + bool updated = false; + + // Convert the lParam into the current structure + var windowPosition = (Structures.WindowPosition) Marshal.PtrToStructure(lParam, typeof(Structures.WindowPosition)); + + // If the window flags indicate no movement then do nothing + if ((windowPosition.Flags & Constants.WindowPositionFlags.NoMove) != 0) + return IntPtr.Zero; + + // If nothing changed then do nothing + if (_lastWindowPosition.IsSameLocationAndSize(windowPosition)) + return IntPtr.Zero; + + // Figure out if the window is being moved or resized + SnapMode snapMode = (_lastWindowPosition.IsSameSize(windowPosition) ? SnapMode.Move : SnapMode.Resize); + + // Get the screen the cursor is currently on + Screen screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); + + // Create a rectangle based on the current working area of the screen + Rectangle snapToBorder = screen.WorkingArea; + + // Deflate the rectangle based on the snap distance + snapToBorder.Inflate(-snapDistance, -snapDistance); + + if (snapMode == SnapMode.Resize) + { + // See if we need to snap on the left + if (windowPosition.Left < snapToBorder.Left) + { + windowPosition.Width += windowPosition.Left - screen.WorkingArea.Left; + windowPosition.Left = screen.WorkingArea.Left; + + updated = true; + } + + // See if we need to snap on the right + if (windowPosition.Right > snapToBorder.Right) + { + windowPosition.Width += (screen.WorkingArea.Right - windowPosition.Right); + + updated = true; + } + + // See if we need to snap to the top + if (windowPosition.Top < snapToBorder.Top) + { + windowPosition.Height += windowPosition.Top - screen.WorkingArea.Top; + windowPosition.Top = screen.WorkingArea.Top; + + updated = true; + } + + // See if we need to snap to the bottom + if (windowPosition.Bottom > snapToBorder.Bottom) + { + windowPosition.Height += (screen.WorkingArea.Bottom - windowPosition.Bottom); + + updated = true; + } + } + else + { + // See if we need to snap on the left + if (windowPosition.Left < snapToBorder.Left) + { + windowPosition.Left = screen.WorkingArea.Left; + updated = true; + } + + // See if we need to snap on the top + if (windowPosition.Top < snapToBorder.Top) + { + windowPosition.Top = screen.WorkingArea.Top; + updated = true; + } + + // See if we need to snap on the right + if (windowPosition.Right > snapToBorder.Right) + { + windowPosition.Left = (screen.WorkingArea.Right - windowPosition.Width); + updated = true; + } + + // See if we need to snap on the bottom + if (windowPosition.Bottom > snapToBorder.Bottom) + { + windowPosition.Top = (screen.WorkingArea.Bottom - windowPosition.Height); + updated = true; + } + } + + var otherWindows = OtherWindows; + + if (otherWindows != null && otherWindows.Count > 0) + { + // Loop over all other windows looking to see if we should stick + foreach (Rect otherWindow in otherWindows) + { + // Get a rectangle with the bounds of the other window + var otherWindowRect = new Rectangle(Convert.ToInt32(otherWindow.Left), Convert.ToInt32(otherWindow.Top), Convert.ToInt32(otherWindow.Width), Convert.ToInt32(otherWindow.Height)); + + // Check the current window left against the other window right + var otherWindowSnapBorder = new Rectangle(otherWindowRect.Right, otherWindowRect.Top, snapDistance, otherWindowRect.Height); + var thisWindowSnapBorder = new Rectangle(windowPosition.Left, windowPosition.Top, 1, windowPosition.Height); + + if (thisWindowSnapBorder.IntersectsWith(otherWindowSnapBorder)) + { + windowPosition.Left = otherWindowRect.Right; + CheckSnapTopAndBottom(ref windowPosition, otherWindowRect, snapMode); + updated = true; + } + + // Check the current window right against the other window left + otherWindowSnapBorder = new Rectangle(otherWindowRect.Left - snapDistance + 1, otherWindowRect.Top, snapDistance, otherWindowRect.Height); + thisWindowSnapBorder = new Rectangle(windowPosition.Right, windowPosition.Top, 1, windowPosition.Height); + + if (thisWindowSnapBorder.IntersectsWith(otherWindowSnapBorder)) + { + windowPosition.Left = otherWindowRect.Left - windowPosition.Width; + CheckSnapTopAndBottom(ref windowPosition, otherWindowRect, snapMode); + updated = true; + } + + // Check the current window bottom against the other window top + otherWindowSnapBorder = new Rectangle(otherWindowRect.Left, otherWindowRect.Top - snapDistance + 1, otherWindowRect.Width, snapDistance); + thisWindowSnapBorder = new Rectangle(windowPosition.Left, windowPosition.Bottom, windowPosition.Width, 1); + + if (thisWindowSnapBorder.IntersectsWith(otherWindowSnapBorder)) + { + windowPosition.Top = otherWindowRect.Top - windowPosition.Height; + CheckSnapLeftAndRight(ref windowPosition, otherWindowRect, snapMode); + updated = true; + } + + // Check the current window top against the other window bottom + otherWindowSnapBorder = new Rectangle(otherWindowRect.Left, otherWindowRect.Bottom, otherWindowRect.Width, snapDistance); + thisWindowSnapBorder = new Rectangle(windowPosition.Left, windowPosition.Top, windowPosition.Width, 1); + + if (thisWindowSnapBorder.IntersectsWith(otherWindowSnapBorder)) + { + windowPosition.Top = otherWindowRect.Bottom; + CheckSnapLeftAndRight(ref windowPosition, otherWindowRect, snapMode); + updated = true; + } + } + } + + // Update the last window position + _lastWindowPosition = windowPosition; + + if (updated) + { + Marshal.StructureToPtr(windowPosition, lParam, true); + handled = true; + } + + return IntPtr.Zero; + } + + private void CheckSnapTopAndBottom(ref Structures.WindowPosition windowPosition, Rectangle otherWindowRect, SnapMode snapMode) + { + int snapDistance = SnapDistance; + + switch (snapMode) + { + case SnapMode.Move: + if (Math.Abs(windowPosition.Top - otherWindowRect.Top) <= snapDistance) + windowPosition.Top = otherWindowRect.Top; + else if (Math.Abs(windowPosition.Bottom - otherWindowRect.Bottom) <= snapDistance) + windowPosition.Top = otherWindowRect.Bottom - windowPosition.Height; + + break; + case SnapMode.Resize: + if (Math.Abs(windowPosition.Top - otherWindowRect.Top) <= snapDistance) + { + windowPosition.Height += (windowPosition.Top - otherWindowRect.Top); + windowPosition.Top = otherWindowRect.Top; + } + else + if (Math.Abs(windowPosition.Bottom - otherWindowRect.Bottom) <= snapDistance) + windowPosition.Height = otherWindowRect.Bottom - windowPosition.Top; + + break; + } + } + + private void CheckSnapLeftAndRight(ref Structures.WindowPosition windowPosition, Rectangle otherWindowRect, SnapMode snapMode) + { + int snapDistance = SnapDistance; + + switch (snapMode) + { + case SnapMode.Move: + if (Math.Abs(windowPosition.Left - otherWindowRect.Left) <= snapDistance) + windowPosition.Left = otherWindowRect.Left; + else if (Math.Abs(windowPosition.Right - otherWindowRect.Right) <= snapDistance) + windowPosition.Left = otherWindowRect.Right - windowPosition.Width; + + break; + case SnapMode.Resize: + if (Math.Abs(windowPosition.Left - otherWindowRect.Left) <= snapDistance) + { + windowPosition.Width += (windowPosition.Left - otherWindowRect.Left); + windowPosition.Left = otherWindowRect.Left; + } + else + if (Math.Abs(windowPosition.Right - otherWindowRect.Right) <= snapDistance) + windowPosition.Width = otherWindowRect.Right - windowPosition.Left; + + break; + } + } + + #endregion + } +}