From 9d3ec68281073c70ac629161247c0493f5af1dd7 Mon Sep 17 00:00:00 2001 From: ButchersBoy Date: Sun, 16 Nov 2014 12:04:34 +0000 Subject: [PATCH] I'll admit, this is the first time I've used git. --- .gitattributes | 22 + .gitignore | 230 ++++++ Dragablz.Test/CollectionTeaserFixture.cs | 113 +++ Dragablz.Test/Dragablz.Test.csproj | 69 ++ Dragablz.Test/Properties/AssemblyInfo.cs | 36 + Dragablz.Test/packages.config | 5 + Dragablz.sln | 68 ++ Dragablz.sln.DotSettings | 2 + Dragablz/CollectionTeaser.cs | 67 ++ Dragablz/Dragablz.csproj | 131 +++ Dragablz/DragablzDragCompletedEventArgs.cs | 53 ++ Dragablz/DragablzDragDeltaEventArgs.cs | 47 ++ Dragablz/DragablzDragStartedEventArgs.cs | 47 ++ Dragablz/DragablzItem.cs | 316 ++++++++ Dragablz/DragablzItemsControl.cs | 258 ++++++ Dragablz/Extensions.cs | 25 + Dragablz/FuncComparer.cs | 22 + Dragablz/HorizontalOrganiser.cs | 11 + Dragablz/HorizontalPositionMonitor.cs | 11 + Dragablz/IInterTabClient.cs | 25 + Dragablz/IItemsOrganiser.cs | 16 + Dragablz/IManualInterTabClient.cs | 8 + Dragablz/INewTabHost.cs | 10 + Dragablz/InterTabController.cs | 65 ++ Dragablz/InterTabTransfer.cs | 96 +++ Dragablz/LinearOrganiser.cs | 186 +++++ Dragablz/LinearPositionMonitor.cs | 46 ++ Dragablz/LocationChangedEventArgs.cs | 29 + Dragablz/MultiComparer.cs | 77 ++ Dragablz/Native.cs | 81 ++ Dragablz/NewTabHost.cs | 30 + Dragablz/OrderChangedEventArgs.cs | 28 + Dragablz/PositionMonitor.cs | 28 + Dragablz/Properties/AssemblyInfo.cs | 57 ++ Dragablz/Properties/Resources.Designer.cs | 63 ++ Dragablz/Properties/Resources.resx | 117 +++ Dragablz/Properties/Settings.Designer.cs | 26 + Dragablz/Properties/Settings.settings | 7 + Dragablz/Referenceless/AnonymousDisposable.cs | 31 + Dragablz/Referenceless/DefaultDisposable.cs | 21 + Dragablz/Referenceless/Disposable.cs | 23 + Dragablz/Referenceless/ICancelable.cs | 9 + Dragablz/TabEmptiedResponse.cs | 14 + Dragablz/TabHeaderDragStartInformation.cs | 51 ++ Dragablz/TabablzControl.cs | 651 +++++++++++++++ Dragablz/TabablzHeaderSizeConverter.cs | 50 ++ Dragablz/TabablzItemStyleSelector.cs | 27 + Dragablz/Themes/Generic.xaml | 760 ++++++++++++++++++ Dragablz/Trapezoid.cs | 119 +++ Dragablz/VerticalOrganiser.cs | 11 + Dragablz/VerticalPositionMonitor.cs | 11 + DragablzDemo/AnotherCommandImplementation.cs | 53 ++ DragablzDemo/App.xaml | 45 ++ DragablzDemo/App.xaml.cs | 11 + DragablzDemo/BasicExampleInterTabClient.cs | 26 + DragablzDemo/BasicExampleMainModel.cs | 163 ++++ DragablzDemo/BasicExampleMainWindow.xaml | 130 +++ DragablzDemo/BasicExampleMainWindow.xaml.cs | 17 + DragablzDemo/BasicExampleTemplateModel.cs | 27 + DragablzDemo/BasicExampleTemplateWindow.xaml | 19 + .../BasicExampleTemplateWindow.xaml.cs | 27 + DragablzDemo/Boot.cs | 51 ++ DragablzDemo/BoundExampleInterTabClient.cs | 21 + DragablzDemo/BoundExampleModel.cs | 42 + DragablzDemo/BoundExampleWindow.xaml | 41 + DragablzDemo/BoundExampleWindow.xaml.cs | 27 + DragablzDemo/DragablzDemo.csproj | 139 ++++ DragablzDemo/Properties/Annotations.cs | 614 ++++++++++++++ DragablzDemo/Properties/AssemblyInfo.cs | 55 ++ DragablzDemo/Properties/Resources.Designer.cs | 63 ++ DragablzDemo/Properties/Resources.resx | 117 +++ DragablzDemo/Properties/Settings.Designer.cs | 26 + DragablzDemo/Properties/Settings.settings | 7 + DragablzDemo/SimpleViewModel.cs | 35 + DragablzDemo/app.config | 3 + 75 files changed, 6065 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Dragablz.Test/CollectionTeaserFixture.cs create mode 100644 Dragablz.Test/Dragablz.Test.csproj create mode 100644 Dragablz.Test/Properties/AssemblyInfo.cs create mode 100644 Dragablz.Test/packages.config create mode 100644 Dragablz.sln create mode 100644 Dragablz.sln.DotSettings create mode 100644 Dragablz/CollectionTeaser.cs create mode 100644 Dragablz/Dragablz.csproj create mode 100644 Dragablz/DragablzDragCompletedEventArgs.cs create mode 100644 Dragablz/DragablzDragDeltaEventArgs.cs create mode 100644 Dragablz/DragablzDragStartedEventArgs.cs create mode 100644 Dragablz/DragablzItem.cs create mode 100644 Dragablz/DragablzItemsControl.cs create mode 100644 Dragablz/Extensions.cs create mode 100644 Dragablz/FuncComparer.cs create mode 100644 Dragablz/HorizontalOrganiser.cs create mode 100644 Dragablz/HorizontalPositionMonitor.cs create mode 100644 Dragablz/IInterTabClient.cs create mode 100644 Dragablz/IItemsOrganiser.cs create mode 100644 Dragablz/IManualInterTabClient.cs create mode 100644 Dragablz/INewTabHost.cs create mode 100644 Dragablz/InterTabController.cs create mode 100644 Dragablz/InterTabTransfer.cs create mode 100644 Dragablz/LinearOrganiser.cs create mode 100644 Dragablz/LinearPositionMonitor.cs create mode 100644 Dragablz/LocationChangedEventArgs.cs create mode 100644 Dragablz/MultiComparer.cs create mode 100644 Dragablz/Native.cs create mode 100644 Dragablz/NewTabHost.cs create mode 100644 Dragablz/OrderChangedEventArgs.cs create mode 100644 Dragablz/PositionMonitor.cs create mode 100644 Dragablz/Properties/AssemblyInfo.cs create mode 100644 Dragablz/Properties/Resources.Designer.cs create mode 100644 Dragablz/Properties/Resources.resx create mode 100644 Dragablz/Properties/Settings.Designer.cs create mode 100644 Dragablz/Properties/Settings.settings create mode 100644 Dragablz/Referenceless/AnonymousDisposable.cs create mode 100644 Dragablz/Referenceless/DefaultDisposable.cs create mode 100644 Dragablz/Referenceless/Disposable.cs create mode 100644 Dragablz/Referenceless/ICancelable.cs create mode 100644 Dragablz/TabEmptiedResponse.cs create mode 100644 Dragablz/TabHeaderDragStartInformation.cs create mode 100644 Dragablz/TabablzControl.cs create mode 100644 Dragablz/TabablzHeaderSizeConverter.cs create mode 100644 Dragablz/TabablzItemStyleSelector.cs create mode 100644 Dragablz/Themes/Generic.xaml create mode 100644 Dragablz/Trapezoid.cs create mode 100644 Dragablz/VerticalOrganiser.cs create mode 100644 Dragablz/VerticalPositionMonitor.cs create mode 100644 DragablzDemo/AnotherCommandImplementation.cs create mode 100644 DragablzDemo/App.xaml create mode 100644 DragablzDemo/App.xaml.cs create mode 100644 DragablzDemo/BasicExampleInterTabClient.cs create mode 100644 DragablzDemo/BasicExampleMainModel.cs create mode 100644 DragablzDemo/BasicExampleMainWindow.xaml create mode 100644 DragablzDemo/BasicExampleMainWindow.xaml.cs create mode 100644 DragablzDemo/BasicExampleTemplateModel.cs create mode 100644 DragablzDemo/BasicExampleTemplateWindow.xaml create mode 100644 DragablzDemo/BasicExampleTemplateWindow.xaml.cs create mode 100644 DragablzDemo/Boot.cs create mode 100644 DragablzDemo/BoundExampleInterTabClient.cs create mode 100644 DragablzDemo/BoundExampleModel.cs create mode 100644 DragablzDemo/BoundExampleWindow.xaml create mode 100644 DragablzDemo/BoundExampleWindow.xaml.cs create mode 100644 DragablzDemo/DragablzDemo.csproj create mode 100644 DragablzDemo/Properties/Annotations.cs create mode 100644 DragablzDemo/Properties/AssemblyInfo.cs create mode 100644 DragablzDemo/Properties/Resources.Designer.cs create mode 100644 DragablzDemo/Properties/Resources.resx create mode 100644 DragablzDemo/Properties/Settings.Designer.cs create mode 100644 DragablzDemo/Properties/Settings.settings create mode 100644 DragablzDemo/SimpleViewModel.cs create mode 100644 DragablzDemo/app.config diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..412eeda --- /dev/null +++ b/.gitattributes @@ -0,0 +1,22 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp +*.sln merge=union +*.csproj merge=union +*.vbproj merge=union +*.fsproj merge=union +*.dbproj merge=union + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2c5c832 --- /dev/null +++ b/.gitignore @@ -0,0 +1,230 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Roslyn cache directories +*.ide/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +#NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding addin-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# If using the old MSBuild-Integrated Package Restore, uncomment this: +#!**/packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# ========================= +# Operating System Files +# ========================= + +# OSX +# ========================= + +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp diff --git a/Dragablz.Test/CollectionTeaserFixture.cs b/Dragablz.Test/CollectionTeaserFixture.cs new file mode 100644 index 0000000..d8ee0ea --- /dev/null +++ b/Dragablz.Test/CollectionTeaserFixture.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Security; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CSharp; +using NUnit.Framework; +using Rhino.Mocks; + +namespace Dragablz.Test +{ + [TestFixture] + public class CollectionTeaserFixture + { + [Test] + public void WillCreateForList() + { + var myList = new ArrayList(); + + CollectionTeaser collectionTeaser; + var result = CollectionTeaser.TryCreate(myList, out collectionTeaser); + + Assert.IsTrue(result); + Assert.IsNotNull(collectionTeaser); + } + + [Test] + public void WillCreateForGenericCollection() + { + var myList = MockRepository.GenerateStub>(); + + CollectionTeaser collectionTeaser; + var result = CollectionTeaser.TryCreate(myList, out collectionTeaser); + + Assert.IsTrue(result); + Assert.IsNotNull(collectionTeaser); + } + + [Test] + public void WillCreateForCollection() + { + var myList = MockRepository.GenerateStub(); + + CollectionTeaser collectionTeaser; + var result = CollectionTeaser.TryCreate(myList, out collectionTeaser); + + Assert.IsFalse(result); + Assert.IsNull(collectionTeaser); + } + + [Test] + public void WillAddForList() + { + var myList = new ArrayList(); + CollectionTeaser collectionTeaser; + Assert.IsTrue(CollectionTeaser.TryCreate(myList, out collectionTeaser)); + + collectionTeaser.Add("i am going to type this in, manually, twice."); + + CollectionAssert.AreEquivalent(new[] {"i am going to type this in, manually, twice."}, myList); + //i didnt really. i copied and pasted it. + } + + [Test] + public void WillRemoveForList() + { + var myList = new ArrayList + { + 1, + 2, + 3, + 4, + 5 + }; + CollectionTeaser collectionTeaser; + Assert.IsTrue(CollectionTeaser.TryCreate(myList, out collectionTeaser)); + + collectionTeaser.Remove(3); + + CollectionAssert.AreEquivalent(new[] {1, 2, 4, 5}, myList); + } + + [Test] + public void WillAddForGenericCollection() + { + var myList = MockRepository.GenerateStub>(); + CollectionTeaser collectionTeaser; + Assert.IsTrue(CollectionTeaser.TryCreate(myList, out collectionTeaser)); + + collectionTeaser.Add("hello"); + + myList.AssertWasCalled(c => c.Add("hello")); + myList.AssertWasNotCalled(c => c.Remove("hello")); + } + + [Test] + public void WillRemoveForGenericCollection() + { + var myList = MockRepository.GenerateStub>(); + CollectionTeaser collectionTeaser; + Assert.IsTrue(CollectionTeaser.TryCreate(myList, out collectionTeaser)); + + collectionTeaser.Remove("bye"); + + myList.AssertWasCalled(c => c.Remove("bye")); + myList.AssertWasNotCalled(c => c.Add("bye")); + + } + } +} diff --git a/Dragablz.Test/Dragablz.Test.csproj b/Dragablz.Test/Dragablz.Test.csproj new file mode 100644 index 0000000..4a8cbd9 --- /dev/null +++ b/Dragablz.Test/Dragablz.Test.csproj @@ -0,0 +1,69 @@ + + + + + Debug + AnyCPU + {BA0DAD50-C09F-4ED6-92D3-185CB5A39225} + Library + Properties + Dragablz.Test + Dragablz.Test + v4.5 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\NUnit.2.6.3\lib\nunit.framework.dll + + + ..\packages\RhinoMocks.3.6.1\lib\net\Rhino.Mocks.dll + + + + + + + + + + + + + + + + + + + {7b11011c-7fd7-4ab0-a1ad-04e940b026de} + Dragablz + + + + + \ No newline at end of file diff --git a/Dragablz.Test/Properties/AssemblyInfo.cs b/Dragablz.Test/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1b8d52a --- /dev/null +++ b/Dragablz.Test/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 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("Dragablz.Test")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Dragablz.Test")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 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)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("0316f46c-d1f9-4c90-a971-3e77dcfca2e4")] + +// 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/Dragablz.Test/packages.config b/Dragablz.Test/packages.config new file mode 100644 index 0000000..bc237b0 --- /dev/null +++ b/Dragablz.Test/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Dragablz.sln b/Dragablz.sln new file mode 100644 index 0000000..18894f3 --- /dev/null +++ b/Dragablz.sln @@ -0,0 +1,68 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dragablz", "Dragablz\Dragablz.csproj", "{7B11011C-7FD7-4AB0-A1AD-04E940B026DE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DragablzDemo", "DragablzDemo\DragablzDemo.csproj", "{63F4A11F-3B6A-43D3-B5A2-B9B9C74D6023}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FuckingAbout", "FuckingAbout\FuckingAbout.csproj", "{0EBD0C56-8190-4DA4-9176-744A4AE8F35D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dragablz.Test", "Dragablz.Test\Dragablz.Test.csproj", "{BA0DAD50-C09F-4ED6-92D3-185CB5A39225}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7B11011C-7FD7-4AB0-A1AD-04E940B026DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B11011C-7FD7-4AB0-A1AD-04E940B026DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B11011C-7FD7-4AB0-A1AD-04E940B026DE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {7B11011C-7FD7-4AB0-A1AD-04E940B026DE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {7B11011C-7FD7-4AB0-A1AD-04E940B026DE}.Debug|x86.ActiveCfg = Debug|Any CPU + {7B11011C-7FD7-4AB0-A1AD-04E940B026DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B11011C-7FD7-4AB0-A1AD-04E940B026DE}.Release|Any CPU.Build.0 = Release|Any CPU + {7B11011C-7FD7-4AB0-A1AD-04E940B026DE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {7B11011C-7FD7-4AB0-A1AD-04E940B026DE}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {7B11011C-7FD7-4AB0-A1AD-04E940B026DE}.Release|x86.ActiveCfg = Release|Any CPU + {63F4A11F-3B6A-43D3-B5A2-B9B9C74D6023}.Debug|Any CPU.ActiveCfg = Debug|x86 + {63F4A11F-3B6A-43D3-B5A2-B9B9C74D6023}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 + {63F4A11F-3B6A-43D3-B5A2-B9B9C74D6023}.Debug|Mixed Platforms.Build.0 = Debug|x86 + {63F4A11F-3B6A-43D3-B5A2-B9B9C74D6023}.Debug|x86.ActiveCfg = Debug|x86 + {63F4A11F-3B6A-43D3-B5A2-B9B9C74D6023}.Debug|x86.Build.0 = Debug|x86 + {63F4A11F-3B6A-43D3-B5A2-B9B9C74D6023}.Release|Any CPU.ActiveCfg = Release|x86 + {63F4A11F-3B6A-43D3-B5A2-B9B9C74D6023}.Release|Mixed Platforms.ActiveCfg = Release|x86 + {63F4A11F-3B6A-43D3-B5A2-B9B9C74D6023}.Release|Mixed Platforms.Build.0 = Release|x86 + {63F4A11F-3B6A-43D3-B5A2-B9B9C74D6023}.Release|x86.ActiveCfg = Release|x86 + {63F4A11F-3B6A-43D3-B5A2-B9B9C74D6023}.Release|x86.Build.0 = Release|x86 + {0EBD0C56-8190-4DA4-9176-744A4AE8F35D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0EBD0C56-8190-4DA4-9176-744A4AE8F35D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0EBD0C56-8190-4DA4-9176-744A4AE8F35D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0EBD0C56-8190-4DA4-9176-744A4AE8F35D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0EBD0C56-8190-4DA4-9176-744A4AE8F35D}.Debug|x86.ActiveCfg = Debug|Any CPU + {0EBD0C56-8190-4DA4-9176-744A4AE8F35D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0EBD0C56-8190-4DA4-9176-744A4AE8F35D}.Release|Any CPU.Build.0 = Release|Any CPU + {0EBD0C56-8190-4DA4-9176-744A4AE8F35D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {0EBD0C56-8190-4DA4-9176-744A4AE8F35D}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0EBD0C56-8190-4DA4-9176-744A4AE8F35D}.Release|x86.ActiveCfg = Release|Any CPU + {BA0DAD50-C09F-4ED6-92D3-185CB5A39225}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA0DAD50-C09F-4ED6-92D3-185CB5A39225}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA0DAD50-C09F-4ED6-92D3-185CB5A39225}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {BA0DAD50-C09F-4ED6-92D3-185CB5A39225}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {BA0DAD50-C09F-4ED6-92D3-185CB5A39225}.Debug|x86.ActiveCfg = Debug|Any CPU + {BA0DAD50-C09F-4ED6-92D3-185CB5A39225}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA0DAD50-C09F-4ED6-92D3-185CB5A39225}.Release|Any CPU.Build.0 = Release|Any CPU + {BA0DAD50-C09F-4ED6-92D3-185CB5A39225}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {BA0DAD50-C09F-4ED6-92D3-185CB5A39225}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {BA0DAD50-C09F-4ED6-92D3-185CB5A39225}.Release|x86.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Dragablz.sln.DotSettings b/Dragablz.sln.DotSettings new file mode 100644 index 0000000..0feab64 --- /dev/null +++ b/Dragablz.sln.DotSettings @@ -0,0 +1,2 @@ + + True \ No newline at end of file diff --git a/Dragablz/CollectionTeaser.cs b/Dragablz/CollectionTeaser.cs new file mode 100644 index 0000000..34199df --- /dev/null +++ b/Dragablz/CollectionTeaser.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Dragablz +{ + internal class CollectionTeaser + { + private readonly Action _addMethod; + private readonly Action _removeMethod; + + private CollectionTeaser(Action addMethod, Action removeMethod) + { + _addMethod = addMethod; + _removeMethod = removeMethod; + } + + public static bool TryCreate(object items, out CollectionTeaser collectionTeaser) + { + collectionTeaser = null; + + var list = items as IList; + if (list != null) + { + collectionTeaser = new CollectionTeaser(i => list.Add(i), list.Remove); + } + else if (items != null) + { + var itemsType = items.GetType(); + var genericCollectionType = typeof (ICollection<>); + + //TODO, *IF* we really wanted to we could get the consumer to inform us of the correct type + //if there are multiple impls. havent got time for this edge case right now though + var collectionImplType = itemsType.GetInterfaces().SingleOrDefault(x => + x.IsGenericType && + x.GetGenericTypeDefinition() == genericCollectionType); + + if (collectionImplType != null) + { + var genericArgType = collectionImplType.GetGenericArguments().First(); + + var addMethodInfo = collectionImplType.GetMethod("Add", new[] {genericArgType}); + var removeMethodInfo = collectionImplType.GetMethod("Remove", new[] { genericArgType }); + + collectionTeaser = new CollectionTeaser( + i => addMethodInfo.Invoke(items, new[] {i}), + i => removeMethodInfo.Invoke(items, new[] {i})); + } + } + + return collectionTeaser != null; + } + + public void Add(object item) + { + _addMethod(item); + } + + public void Remove(object item) + { + _removeMethod(item); + } + } +} diff --git a/Dragablz/Dragablz.csproj b/Dragablz/Dragablz.csproj new file mode 100644 index 0000000..93d80e8 --- /dev/null +++ b/Dragablz/Dragablz.csproj @@ -0,0 +1,131 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {7B11011C-7FD7-4AB0-A1AD-04E940B026DE} + library + Properties + Dragablz + Dragablz + v4.5 + + + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + + + + + + + + 4.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + Designer + + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + \ No newline at end of file diff --git a/Dragablz/DragablzDragCompletedEventArgs.cs b/Dragablz/DragablzDragCompletedEventArgs.cs new file mode 100644 index 0000000..ee26c4b --- /dev/null +++ b/Dragablz/DragablzDragCompletedEventArgs.cs @@ -0,0 +1,53 @@ +using System; +using System.Windows; +using System.Windows.Controls.Primitives; + +namespace Dragablz +{ + public delegate void DragablzDragCompletedEventHandler(object sender, DragablzDragCompletedEventArgs e); + + public class DragablzDragCompletedEventArgs : RoutedEventArgs + { + private readonly DragablzItem _dragablzItem; + private readonly DragCompletedEventArgs _dragCompletedEventArgs; + + public DragablzDragCompletedEventArgs(DragablzItem dragablzItem, DragCompletedEventArgs dragCompletedEventArgs) + { + if (dragablzItem == null) throw new ArgumentNullException("dragablzItem"); + if (dragCompletedEventArgs == null) throw new ArgumentNullException("dragCompletedEventArgs"); + + _dragablzItem = dragablzItem; + _dragCompletedEventArgs = dragCompletedEventArgs; + } + + public DragablzDragCompletedEventArgs(RoutedEvent routedEvent, DragablzItem dragablzItem, DragCompletedEventArgs dragCompletedEventArgs) + : base(routedEvent) + { + if (dragablzItem == null) throw new ArgumentNullException("dragablzItem"); + if (dragCompletedEventArgs == null) throw new ArgumentNullException("dragCompletedEventArgs"); + + _dragablzItem = dragablzItem; + _dragCompletedEventArgs = dragCompletedEventArgs; + } + + public DragablzDragCompletedEventArgs(RoutedEvent routedEvent, object source, DragablzItem dragablzItem, DragCompletedEventArgs dragCompletedEventArgs) + : base(routedEvent, source) + { + if (dragablzItem == null) throw new ArgumentNullException("dragablzItem"); + if (dragCompletedEventArgs == null) throw new ArgumentNullException("dragCompletedEventArgs"); + + _dragablzItem = dragablzItem; + _dragCompletedEventArgs = dragCompletedEventArgs; + } + + public DragablzItem DragablzItem + { + get { return _dragablzItem; } + } + + public DragCompletedEventArgs DragCompletedEventArgs + { + get { return _dragCompletedEventArgs; } + } + } +} \ No newline at end of file diff --git a/Dragablz/DragablzDragDeltaEventArgs.cs b/Dragablz/DragablzDragDeltaEventArgs.cs new file mode 100644 index 0000000..2a95cb8 --- /dev/null +++ b/Dragablz/DragablzDragDeltaEventArgs.cs @@ -0,0 +1,47 @@ +using System; +using System.Windows; +using System.Windows.Controls.Primitives; + +namespace Dragablz +{ + public delegate void DragablzDragDeltaEventHandler(object sender, DragablzDragDeltaEventArgs e); + + public class DragablzDragDeltaEventArgs : RoutedEventArgs + { + private readonly DragablzItem _dragablzItem; + private readonly DragDeltaEventArgs _dragDeltaEventArgs; + + public DragablzDragDeltaEventArgs(DragablzItem dragablzItem, DragDeltaEventArgs dragDeltaEventArgs) + { + if (dragablzItem == null) throw new ArgumentNullException("dragablzItem"); + if (dragDeltaEventArgs == null) throw new ArgumentNullException("dragDeltaEventArgs"); + + _dragablzItem = dragablzItem; + _dragDeltaEventArgs = dragDeltaEventArgs; + } + + public DragablzDragDeltaEventArgs(RoutedEvent routedEvent, DragablzItem dragablzItem, DragDeltaEventArgs dragDeltaEventArgs) : base(routedEvent) + { + _dragablzItem = dragablzItem; + _dragDeltaEventArgs = dragDeltaEventArgs; + } + + public DragablzDragDeltaEventArgs(RoutedEvent routedEvent, object source, DragablzItem dragablzItem, DragDeltaEventArgs dragDeltaEventArgs) : base(routedEvent, source) + { + _dragablzItem = dragablzItem; + _dragDeltaEventArgs = dragDeltaEventArgs; + } + + public DragablzItem DragablzItem + { + get { return _dragablzItem; } + } + + public DragDeltaEventArgs DragDeltaEventArgs + { + get { return _dragDeltaEventArgs; } + } + + public bool Cancel { get; set; } + } +} \ No newline at end of file diff --git a/Dragablz/DragablzDragStartedEventArgs.cs b/Dragablz/DragablzDragStartedEventArgs.cs new file mode 100644 index 0000000..b356efe --- /dev/null +++ b/Dragablz/DragablzDragStartedEventArgs.cs @@ -0,0 +1,47 @@ +using System; +using System.Windows; +using System.Windows.Controls.Primitives; + +namespace Dragablz +{ + public delegate void DragablzDragStartedEventHandler(object sender, DragablzDragStartedEventArgs e); + + public class DragablzDragStartedEventArgs : RoutedEventArgs + { + private readonly DragablzItem _dragablzItem; + private readonly DragStartedEventArgs _dragStartedEventArgs; + + public DragablzDragStartedEventArgs(DragablzItem dragablzItem, DragStartedEventArgs dragStartedEventArgs) + { + if (dragablzItem == null) throw new ArgumentNullException("dragablzItem"); + if (dragStartedEventArgs == null) throw new ArgumentNullException("dragStartedEventArgs"); + + _dragablzItem = dragablzItem; + _dragStartedEventArgs = dragStartedEventArgs; + } + + public DragablzDragStartedEventArgs(RoutedEvent routedEvent, DragablzItem dragablzItem, DragStartedEventArgs dragStartedEventArgs) + : base(routedEvent) + { + _dragablzItem = dragablzItem; + _dragStartedEventArgs = dragStartedEventArgs; + } + + public DragablzDragStartedEventArgs(RoutedEvent routedEvent, object source, DragablzItem dragablzItem, DragStartedEventArgs dragStartedEventArgs) + : base(routedEvent, source) + { + _dragablzItem = dragablzItem; + _dragStartedEventArgs = dragStartedEventArgs; + } + + public DragablzItem DragablzItem + { + get { return _dragablzItem; } + } + + public DragStartedEventArgs DragStartedEventArgs + { + get { return _dragStartedEventArgs; } + } + } +} \ No newline at end of file diff --git a/Dragablz/DragablzItem.cs b/Dragablz/DragablzItem.cs new file mode 100644 index 0000000..ac0d230 --- /dev/null +++ b/Dragablz/DragablzItem.cs @@ -0,0 +1,316 @@ +using System; +using System.Dynamic; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Threading; +using Dragablz.Referenceless; + +namespace Dragablz +{ + [TemplatePart(Name = ThumbPartName, Type = typeof(Thumb))] + public class DragablzItem : ContentControl + { + public const string ThumbPartName = "PART_Thumb"; + + private IDisposable _templateSubscriptions; + + private bool _seizeDragWithTemplate; + private Action _dragSeizedContinuation; + + static DragablzItem() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(DragablzItem), new FrameworkPropertyMetadata(typeof(DragablzItem))); + TabItem t; + } + + public static readonly DependencyProperty XProperty = DependencyProperty.Register( + "X", typeof (double), typeof (DragablzItem), new PropertyMetadata(default(double), OnXChanged)); + + public double X + { + get { return (double) GetValue(XProperty); } + set { SetValue(XProperty, value); } + } + + public static readonly RoutedEvent XChangedEvent = + EventManager.RegisterRoutedEvent( + "XChanged", + RoutingStrategy.Bubble, + typeof(RoutedPropertyChangedEventHandler), + typeof(DragablzItem)); + + public event RoutedPropertyChangedEventHandler XChanged + { + add { AddHandler(XChangedEvent, value); } + remove { RemoveHandler(IsDraggingChangedEvent, value); } + } + + private static void OnXChanged( + DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var instance = (DragablzItem)d; + var args = new RoutedPropertyChangedEventArgs( + (double)e.OldValue, + (double)e.NewValue) + { + RoutedEvent = XChangedEvent + }; + instance.RaiseEvent(args); + } + + public static readonly DependencyProperty YProperty = DependencyProperty.Register( + "Y", typeof (double), typeof (DragablzItem), new PropertyMetadata(default(double), OnYChanged)); + + public double Y + { + get { return (double) GetValue(YProperty); } + set { SetValue(YProperty, value); } + } + + public static readonly RoutedEvent YChangedEvent = + EventManager.RegisterRoutedEvent( + "YChanged", + RoutingStrategy.Bubble, + typeof(RoutedPropertyChangedEventHandler), + typeof(DragablzItem)); + + public event RoutedPropertyChangedEventHandler YChanged + { + add { AddHandler(YChangedEvent, value); } + remove { RemoveHandler(IsDraggingChangedEvent, value); } + } + + private static void OnYChanged( + DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var instance = (DragablzItem)d; + var args = new RoutedPropertyChangedEventArgs( + (double)e.OldValue, + (double)e.NewValue) + { + RoutedEvent = YChangedEvent + }; + instance.RaiseEvent(args); + } + + private static readonly DependencyPropertyKey IsDraggingPropertyKey = + DependencyProperty.RegisterReadOnly( + "IsDragging", typeof (bool), typeof (DragablzItem), + new PropertyMetadata(default(bool), OnIsDraggingChanged)); + + public static readonly DependencyProperty IsDraggingProperty = + IsDraggingPropertyKey.DependencyProperty; + + public bool IsDragging + { + get { return (bool) GetValue(IsDraggingProperty); } + internal set { SetValue(IsDraggingPropertyKey, value); } + } + + public static readonly RoutedEvent IsDraggingChangedEvent = + EventManager.RegisterRoutedEvent( + "IsDraggingChanged", + RoutingStrategy.Bubble, + typeof (RoutedPropertyChangedEventHandler), + typeof (DragablzItem)); + + public event RoutedPropertyChangedEventHandler IsDraggingChanged + { + add { AddHandler(IsDraggingChangedEvent, value); } + remove { RemoveHandler(IsDraggingChangedEvent, value); } + } + + private static readonly DependencyPropertyKey IsSiblingDraggingPropertyKey = + DependencyProperty.RegisterReadOnly( + "IsSiblingDragging", typeof (bool), typeof (DragablzItem), + new PropertyMetadata(default(bool), OnIsSiblingDraggingChanged)); + + public static readonly DependencyProperty IsSiblingDraggingProperty = + IsSiblingDraggingPropertyKey.DependencyProperty; + + public bool IsSiblingDragging + { + get { return (bool) GetValue(IsSiblingDraggingProperty); } + internal set { SetValue(IsSiblingDraggingPropertyKey, value); } + } + + public static readonly RoutedEvent IsSiblingDraggingChangedEvent = + EventManager.RegisterRoutedEvent( + "IsSiblingDraggingChanged", + RoutingStrategy.Bubble, + typeof (RoutedPropertyChangedEventHandler), + typeof (DragablzItem)); + + public event RoutedPropertyChangedEventHandler IsSiblingDraggingChanged + { + add { AddHandler(IsSiblingDraggingChangedEvent, value); } + remove { RemoveHandler(IsSiblingDraggingChangedEvent, value); } + } + + private static void OnIsSiblingDraggingChanged( + DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var instance = (DragablzItem) d; + var args = new RoutedPropertyChangedEventArgs( + (bool) e.OldValue, + (bool) e.NewValue) + { + RoutedEvent = IsSiblingDraggingChangedEvent + }; + instance.RaiseEvent(args); + } + + private static void OnIsDraggingChanged( + DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var instance = (DragablzItem)d; + var args = new RoutedPropertyChangedEventArgs( + (bool) e.OldValue, + (bool) e.NewValue) {RoutedEvent = IsDraggingChangedEvent}; + instance.RaiseEvent(args); + } + + public static readonly RoutedEvent DragStarted = + EventManager.RegisterRoutedEvent( + "DragStarted", + RoutingStrategy.Bubble, + typeof(DragablzDragStartedEventHandler), + typeof(DragablzItem)); + + protected void OnDragStarted(DragablzDragStartedEventArgs e) + { + RaiseEvent(e); + } + + public static readonly RoutedEvent DragDelta = + EventManager.RegisterRoutedEvent( + "DragDelta", + RoutingStrategy.Bubble, + typeof (DragablzDragDeltaEventHandler), + typeof (DragablzItem)); + + protected void OnDragDelta(DragablzDragDeltaEventArgs e) + { + RaiseEvent(e); + } + + public static readonly RoutedEvent PreviewDragDelta = + EventManager.RegisterRoutedEvent( + "PreviewDragDelta", + RoutingStrategy.Tunnel, + typeof(DragablzDragDeltaEventHandler), + typeof(DragablzItem)); + + protected void OnPreviewDragDelta(DragablzDragDeltaEventArgs e) + { + RaiseEvent(e); + } + + public static readonly RoutedEvent DragCompleted = + EventManager.RegisterRoutedEvent( + "DragCompleted", + RoutingStrategy.Bubble, + typeof(DragablzDragCompletedEventHandler), + typeof(DragablzItem)); + + protected void OnDragCompleted(DragCompletedEventArgs e) + { + var args = new DragablzDragCompletedEventArgs(DragCompleted, this, e); + RaiseEvent(args); + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (_templateSubscriptions != null) + { + _templateSubscriptions.Dispose(); + _templateSubscriptions = null; + } + + var thumb = GetTemplateChild(ThumbPartName) as Thumb; + if (thumb != null) + { + thumb.DragStarted += ThumbOnDragStarted; + thumb.DragDelta += ThumbOnDragDelta; + thumb.DragCompleted += ThumbOnDragCompleted; + } + + if (_seizeDragWithTemplate && thumb != null) + { + if (_dragSeizedContinuation != null) + _dragSeizedContinuation(this); + _dragSeizedContinuation = null; + + Dispatcher.BeginInvoke(new Action(() => thumb.RaiseEvent(new MouseButtonEventArgs(InputManager.Current.PrimaryMouseDevice, + 0, + MouseButton.Left) {RoutedEvent = MouseLeftButtonDownEvent}))); + + /* + Console.WriteLine("MungeCapture X =" + X); + thumb.RaiseEvent(new MouseButtonEventArgs(InputManager.Current.PrimaryMouseDevice, + 0, + MouseButton.Left) {RoutedEvent = MouseLeftButtonDownEvent}); + Console.WriteLine("MungeCapture X.1 =" + X); + */ + } + _seizeDragWithTemplate = false; + + _templateSubscriptions = Disposable.Create(() => + { + if (thumb == null) return; + thumb.DragStarted -= ThumbOnDragStarted; + thumb.DragDelta -= ThumbOnDragDelta; + thumb.DragCompleted -= ThumbOnDragCompleted; + }); + } + + internal void InstigateDrag(Action continuation) + { + _dragSeizedContinuation = continuation; + var thumb = GetTemplateChild(ThumbPartName) as Thumb; + if (thumb != null) + { + thumb.CaptureMouse(); + } + else + _seizeDragWithTemplate = true; + } + + internal Point MouseAtDragStart { get; set; } + + private void ThumbOnDragCompleted(object sender, DragCompletedEventArgs dragCompletedEventArgs) + { + MouseAtDragStart = new Point(); + OnDragCompleted(dragCompletedEventArgs); + } + + private void ThumbOnDragDelta(object sender, DragDeltaEventArgs dragDeltaEventArgs) + { + var thumb = (Thumb) sender; + + var previewEventArgs = new DragablzDragDeltaEventArgs(PreviewDragDelta, this, dragDeltaEventArgs); + OnPreviewDragDelta(previewEventArgs); + if (previewEventArgs.Cancel) + thumb.CancelDrag(); + if (!previewEventArgs.Handled) + { + var eventArgs = new DragablzDragDeltaEventArgs(DragDelta, this, dragDeltaEventArgs); + OnDragDelta(eventArgs); + if (eventArgs.Cancel) + thumb.CancelDrag(); + } + } + + private void ThumbOnDragStarted(object sender, DragStartedEventArgs dragStartedEventArgs) + { + Console.WriteLine("ThumbOnDragStarted {0},{1}", dragStartedEventArgs.HorizontalOffset, dragStartedEventArgs.VerticalOffset); + + MouseAtDragStart = Mouse.GetPosition(this); + OnDragStarted(new DragablzDragStartedEventArgs(DragStarted, this, dragStartedEventArgs)); + } + } +} \ No newline at end of file diff --git a/Dragablz/DragablzItemsControl.cs b/Dragablz/DragablzItemsControl.cs new file mode 100644 index 0000000..31465dd --- /dev/null +++ b/Dragablz/DragablzItemsControl.cs @@ -0,0 +1,258 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Threading; + +namespace Dragablz +{ + /// + /// Items control which typically uses a canvas and + /// + public class DragablzItemsControl : ItemsControl + { + private readonly IList _itemsPendingInitialArrangement = new List(); + private object[] _previousSortQueryResult; + + static DragablzItemsControl() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(DragablzItemsControl), new FrameworkPropertyMetadata(typeof(DragablzItemsControl))); + } + + public DragablzItemsControl() + { + ItemContainerGenerator.StatusChanged += ItemContainerGeneratorOnStatusChanged; + ItemContainerGenerator.ItemsChanged += ItemContainerGeneratorOnItemsChanged; + AddHandler(DragablzItem.XChangedEvent, new RoutedPropertyChangedEventHandler(ItemXChanged)); + AddHandler(DragablzItem.YChangedEvent, new RoutedPropertyChangedEventHandler(ItemYChanged)); + AddHandler(DragablzItem.DragDelta, new DragablzDragDeltaEventHandler(ItemDragDelta)); + AddHandler(DragablzItem.DragCompleted, new DragablzDragCompletedEventHandler(ItemDragCompleted)); + AddHandler(DragablzItem.DragStarted, new DragablzDragStartedEventHandler(ItemDragStarted)); + } + + private void ItemContainerGeneratorOnItemsChanged(object sender, ItemsChangedEventArgs itemsChangedEventArgs) + { + //throw new NotImplementedException(); + } + + protected override void ClearContainerForItemOverride(DependencyObject element, object item) + { + base.ClearContainerForItemOverride(element, item); + + ItemsOrganiser.Organise(new Size(ItemsPresenterWidth, ItemsPresenterHeight), DragablzItems()); + var measure = ItemsOrganiser.Measure(DragablzItems()); + ItemsPresenterWidth = measure.Width; + ItemsPresenterHeight = measure.Height; + } + + public static readonly DependencyProperty ItemsOrganiserProperty = DependencyProperty.Register( + "ItemsOrganiser", typeof (IItemsOrganiser), typeof (DragablzItemsControl), new PropertyMetadata(default(IItemsOrganiser))); + + public IItemsOrganiser ItemsOrganiser + { + get { return (IItemsOrganiser) GetValue(ItemsOrganiserProperty); } + set { SetValue(ItemsOrganiserProperty, value); } + } + + public static readonly DependencyProperty PositionMonitorProperty = DependencyProperty.Register( + "PositionMonitor", typeof (PositionMonitor), typeof (DragablzItemsControl), new PropertyMetadata(default(PositionMonitor))); + + public PositionMonitor PositionMonitor + { + get { return (PositionMonitor) GetValue(PositionMonitorProperty); } + set { SetValue(PositionMonitorProperty, value); } + } + + private static readonly DependencyPropertyKey ItemsPresenterWidthPropertyKey = + DependencyProperty.RegisterReadOnly( + "ItemsPresenterWidth", typeof(double), typeof (DragablzItemsControl), + new PropertyMetadata(default(double))); + + public static readonly DependencyProperty ItemsPresenterWidthProperty = + ItemsPresenterWidthPropertyKey.DependencyProperty; + + public double ItemsPresenterWidth + { + get { return (double) GetValue(ItemsPresenterWidthProperty); } + private set { SetValue(ItemsPresenterWidthPropertyKey, value); } + } + + private static readonly DependencyPropertyKey ItemsPresenterHeightPropertyKey = + DependencyProperty.RegisterReadOnly( + "ItemsPresenterHeight", typeof (double), typeof (DragablzItemsControl), + new PropertyMetadata(default(double))); + + public static readonly DependencyProperty ItemsPresenterHeightProperty = + ItemsPresenterHeightPropertyKey.DependencyProperty; + + public double ItemsPresenterHeight + { + get { return (double) GetValue(ItemsPresenterHeightProperty); } + private set { SetValue(ItemsPresenterHeightPropertyKey, value); } + } + + private void ItemContainerGeneratorOnStatusChanged(object sender, EventArgs eventArgs) + { + if (ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated) return; + + InvalidateMeasure(); + //extra kick + Dispatcher.BeginInvoke(new Action(InvalidateMeasure), DispatcherPriority.Render); + } + + protected override bool IsItemItsOwnContainerOverride(object item) + { + var dragablzItem = item as DragablzItem; + if (dragablzItem == null) return false; + + _itemsPendingInitialArrangement.Add(dragablzItem); + + return false; + } + + protected override DependencyObject GetContainerForItemOverride() + { + var result = new DragablzItem(); + + _itemsPendingInitialArrangement.Add(result); + + return result; + } + + protected override Size MeasureOverride(Size constraint) + { + if (ItemsOrganiser == null) return base.MeasureOverride(constraint); + + if (LockedMeasure.HasValue) + { + ItemsPresenterWidth = LockedMeasure.Value.Width; + ItemsPresenterHeight = LockedMeasure.Value.Height; + return LockedMeasure.Value; + } + + var dragablzItems = DragablzItems().ToList(); + + var maxConstraint = new Size(double.PositiveInfinity, double.PositiveInfinity); + + ItemsOrganiser.Organise(maxConstraint, dragablzItems); + var measure = ItemsOrganiser.Measure(dragablzItems); + ItemsPresenterWidth = measure.Width; + ItemsPresenterHeight = measure.Height; + + var width = double.IsInfinity(constraint.Width) ? measure.Width : constraint.Width; + var height = double.IsInfinity(constraint.Height) ? measure.Height : constraint.Height; + + return new Size(width, height); + } + + internal void InstigateDrag(object item, Action continuation) + { + var dragablzItem = (DragablzItem)ItemContainerGenerator.ContainerFromItem(item); + dragablzItem.InstigateDrag(continuation); + } + + internal IEnumerable DragablzItems() + { + return this.Containers(); + } + + internal Size? LockedMeasure { get; set; } + + private void ItemDragStarted(object sender, DragablzDragStartedEventArgs dragablzDragStartedEventArgs) + { + foreach (var dragableItem in DragablzItems() + .Except(new [] { dragablzDragStartedEventArgs.DragablzItem})) + { + dragableItem.IsSiblingDragging = true; + } + dragablzDragStartedEventArgs.DragablzItem.IsDragging = true; + + dragablzDragStartedEventArgs.Handled = true; + } + + private void ItemDragCompleted(object sender, DragablzDragCompletedEventArgs eventArgs) + { + var dragablzItems = DragablzItems() + .Select(i => + { + i.IsDragging = false; + i.IsSiblingDragging = false; + return i; + }) + .ToList(); + + if (ItemsOrganiser != null) + { + var bounds = new Size(ActualWidth, ActualHeight); + ItemsOrganiser.OrganiseOnDragCompleted(bounds, + dragablzItems.Except(new[] { eventArgs.DragablzItem }), + eventArgs.DragablzItem); + } + + eventArgs.Handled = true; + + if (ItemsOrganiser == null) return; + var measure = ItemsOrganiser.Measure(dragablzItems); + ItemsPresenterWidth = measure.Width; + ItemsPresenterHeight = measure.Height; + } + + private void ItemDragDelta(object sender, DragablzDragDeltaEventArgs eventArgs) + { + var bounds = new Size(ItemsPresenterWidth, ItemsPresenterHeight); + var desiredLocation = new Point( + eventArgs.DragablzItem.X + eventArgs.DragDeltaEventArgs.HorizontalChange, + eventArgs.DragablzItem.Y += eventArgs.DragDeltaEventArgs.VerticalChange + ); + if (ItemsOrganiser != null) + desiredLocation = ItemsOrganiser.ConstrainLocation(bounds, desiredLocation, eventArgs.DragablzItem.DesiredSize); + + eventArgs.DragablzItem.X = desiredLocation.X; + eventArgs.DragablzItem.Y = desiredLocation.Y; + + if (ItemsOrganiser != null) + ItemsOrganiser.OrganiseOnDrag( + bounds, + DragablzItems().Except(new[] {eventArgs.DragablzItem}), + eventArgs.DragablzItem); + + eventArgs.DragablzItem.BringIntoView(); + + eventArgs.Handled = true; + } + + private void ItemXChanged(object sender, RoutedPropertyChangedEventArgs routedPropertyChangedEventArgs) + { + UpdateMonitor(routedPropertyChangedEventArgs); + } + + private void ItemYChanged(object sender, RoutedPropertyChangedEventArgs routedPropertyChangedEventArgs) + { + UpdateMonitor(routedPropertyChangedEventArgs); + } + + private void UpdateMonitor(RoutedEventArgs routedPropertyChangedEventArgs) + { + if (PositionMonitor == null) return; + + var dragablzItem = (DragablzItem) routedPropertyChangedEventArgs.OriginalSource; + + if (!Equals(ItemsControlFromItemContainer(dragablzItem), this)) return; + + PositionMonitor.OnLocationChanged(new LocationChangedEventArgs(dragablzItem.Content, new Point(dragablzItem.X, dragablzItem.Y))); + + var linearPositionMonitor = PositionMonitor as LinearPositionMonitor; + if (linearPositionMonitor == null) return; + + var sortedItems = linearPositionMonitor.Sort(this.Containers()).Select(di => di.Content).ToArray(); + if (_previousSortQueryResult == null || !_previousSortQueryResult.SequenceEqual(sortedItems)) + linearPositionMonitor.OnOrderChanged(new OrderChangedEventArgs(_previousSortQueryResult, sortedItems)); + + _previousSortQueryResult = sortedItems; + } + } +} diff --git a/Dragablz/Extensions.cs b/Dragablz/Extensions.cs new file mode 100644 index 0000000..388b5d1 --- /dev/null +++ b/Dragablz/Extensions.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using System.Linq; +using System.Windows.Controls; + +namespace Dragablz +{ + internal static class Extensions + { + public static IEnumerable Containers(this ItemsControl itemsControl) where TContainer : class + { + for (var i = 0; i < itemsControl.ItemContainerGenerator.Items.Count; i++) + { + var container = itemsControl.ItemContainerGenerator.ContainerFromIndex(i) as TContainer; + if (container != null) + yield return container; + } + } + + public static IEnumerable Except(this IEnumerable first, params TObject[] second) + { + return first.Except((IEnumerable)second); + } + + } +} \ No newline at end of file diff --git a/Dragablz/FuncComparer.cs b/Dragablz/FuncComparer.cs new file mode 100644 index 0000000..0e621ba --- /dev/null +++ b/Dragablz/FuncComparer.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace Dragablz +{ + internal class FuncComparer : IComparer + { + private readonly Func _comparer; + + public FuncComparer(Func comparer) + { + if (comparer == null) throw new ArgumentNullException("comparer"); + + _comparer = comparer; + } + + public int Compare(TObject x, TObject y) + { + return _comparer(x, y); + } + } +} \ No newline at end of file diff --git a/Dragablz/HorizontalOrganiser.cs b/Dragablz/HorizontalOrganiser.cs new file mode 100644 index 0000000..4fbae92 --- /dev/null +++ b/Dragablz/HorizontalOrganiser.cs @@ -0,0 +1,11 @@ +using System.Windows.Controls; + +namespace Dragablz +{ + public class HorizontalOrganiser : LinearOrganiser + { + public HorizontalOrganiser() : base(Orientation.Horizontal) + { + } + } +} \ No newline at end of file diff --git a/Dragablz/HorizontalPositionMonitor.cs b/Dragablz/HorizontalPositionMonitor.cs new file mode 100644 index 0000000..db73b9f --- /dev/null +++ b/Dragablz/HorizontalPositionMonitor.cs @@ -0,0 +1,11 @@ +using System.Windows.Controls; + +namespace Dragablz +{ + public class HorizontalPositionMonitor : LinearPositionMonitor + { + public HorizontalPositionMonitor() : base(Orientation.Horizontal) + { + } + } +} \ No newline at end of file diff --git a/Dragablz/IInterTabClient.cs b/Dragablz/IInterTabClient.cs new file mode 100644 index 0000000..e948b6a --- /dev/null +++ b/Dragablz/IInterTabClient.cs @@ -0,0 +1,25 @@ +using System.Windows; + +namespace Dragablz +{ + /// + /// Implementors should provide mechanisms for providing new windows and closing old windows. + /// + public interface IInterTabClient + { + /// + /// Provide a new host window so a tab can be teared from an existing window into a new window. + /// + /// + /// Provides the partition where the drag operation was initiated. + /// + INewTabHost GetNewHost(IInterTabClient interTabClient, object partition); + /// + /// Called when a tab has been emptied, and thus typically a window needs closing. + /// + /// + /// + /// + TabEmptiedResponse TabEmptiedHandler(TabablzControl tabControl, Window window); + } +} \ No newline at end of file diff --git a/Dragablz/IItemsOrganiser.cs b/Dragablz/IItemsOrganiser.cs new file mode 100644 index 0000000..36e836e --- /dev/null +++ b/Dragablz/IItemsOrganiser.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Windows; + +namespace Dragablz +{ + public interface IItemsOrganiser + { + void Organise(Size bounds, IEnumerable items); + void OrganiseOnDrag(Size bounds, IEnumerable siblingItems, DragablzItem dragItem); + void OrganiseOnDragCompleted(Size bounds, IEnumerable siblingItems, DragablzItem dragItem); + Point ConstrainLocation(Size bounds, Point desiredLocation, Size itemDesiredSize); + Size Measure(IEnumerable items); + } +} diff --git a/Dragablz/IManualInterTabClient.cs b/Dragablz/IManualInterTabClient.cs new file mode 100644 index 0000000..5d3f70a --- /dev/null +++ b/Dragablz/IManualInterTabClient.cs @@ -0,0 +1,8 @@ +namespace Dragablz +{ + public interface IManualInterTabClient : IInterTabClient + { + void Add(object item); + void Remove(object item); + } +} \ No newline at end of file diff --git a/Dragablz/INewTabHost.cs b/Dragablz/INewTabHost.cs new file mode 100644 index 0000000..febd895 --- /dev/null +++ b/Dragablz/INewTabHost.cs @@ -0,0 +1,10 @@ +using System.Windows; + +namespace Dragablz +{ + public interface INewTabHost + { + Window Window { get; } + TabablzControl TabablzControl { get; } + } +} \ No newline at end of file diff --git a/Dragablz/InterTabController.cs b/Dragablz/InterTabController.cs new file mode 100644 index 0000000..cbfe0ab --- /dev/null +++ b/Dragablz/InterTabController.cs @@ -0,0 +1,65 @@ +using System.Dynamic; +using System.Windows; + +namespace Dragablz +{ + public class InterTabController : FrameworkElement + { + public InterTabController() + { + HorizontalPopoutGrace = 8; + VerticalPopoutGrace = 8; + MoveWindowWithSolitaryTabs = true; + } + + public static readonly DependencyProperty HorizontalPopoutGraceProperty = DependencyProperty.Register( + "HorizontalPopoutGrace", typeof (double), typeof (InterTabController), new PropertyMetadata(8.0)); + + public double HorizontalPopoutGrace + { + get { return (double) GetValue(HorizontalPopoutGraceProperty); } + set { SetValue(HorizontalPopoutGraceProperty, value); } + } + + public static readonly DependencyProperty VerticalPopoutGraceProperty = DependencyProperty.Register( + "VerticalPopoutGrace", typeof (double), typeof (InterTabController), new PropertyMetadata(8.0)); + + public double VerticalPopoutGrace + { + get { return (double) GetValue(VerticalPopoutGraceProperty); } + set { SetValue(VerticalPopoutGraceProperty, value); } + } + + public static readonly DependencyProperty MoveWindowWithSolitaryTabsProperty = DependencyProperty.Register( + "MoveWindowWithSolitaryTabs", typeof (bool), typeof (InterTabController), new PropertyMetadata(true)); + + public bool MoveWindowWithSolitaryTabs + { + get { return (bool) GetValue(MoveWindowWithSolitaryTabsProperty); } + set { SetValue(MoveWindowWithSolitaryTabsProperty, value); } + } + + public static readonly DependencyProperty InterTabClientProperty = DependencyProperty.Register( + "InterTabClient", typeof (IInterTabClient), typeof (InterTabController), + new PropertyMetadata(default(IInterTabClient))); + + public IInterTabClient InterTabClient + { + get { return (IInterTabClient) GetValue(InterTabClientProperty); } + set { SetValue(InterTabClientProperty, value); } + } + + public static readonly DependencyProperty PartitionProperty = DependencyProperty.Register( + "Partition", typeof (object), typeof (InterTabController), new PropertyMetadata(default(object))); + + /// + /// The partition allows on or more tab environments in a single application. Only tabs which have a tab controller + /// with a common partition will be allowed to have tabs dragged between them. null is a valid partition (i.e global). + /// + public object Partition + { + get { return (object) GetValue(PartitionProperty); } + set { SetValue(PartitionProperty, value); } + } + } +} \ No newline at end of file diff --git a/Dragablz/InterTabTransfer.cs b/Dragablz/InterTabTransfer.cs new file mode 100644 index 0000000..e8c7c19 --- /dev/null +++ b/Dragablz/InterTabTransfer.cs @@ -0,0 +1,96 @@ +using System; +using System.Windows; +using System.Windows.Controls; + +namespace Dragablz +{ + internal enum InterTabTransferReason + { + Breach, + Reentry + } + + internal class InterTabTransfer + { + private readonly object _item; + private readonly DragablzItem _originatorContainer; + private readonly Orientation _breachOrientation; + private readonly Point _dragStartWindowOffset; + private readonly Point _dragStartItemOffset; + private readonly Point _itemPositionWithinHeader; + private readonly Size _itemSize; + private readonly InterTabTransferReason _transferReason; + + public InterTabTransfer( + object item, DragablzItem originatorContainer, + Orientation breachOrientation, + Point dragStartWindowOffset, Point dragStartItemOffset, + Point itemPositionWithinHeader, Size itemSize) + { + if (item == null) throw new ArgumentNullException("item"); + if (originatorContainer == null) throw new ArgumentNullException("originatorContainer"); + + _transferReason = InterTabTransferReason.Breach; + + _item = item; + _originatorContainer = originatorContainer; + _breachOrientation = breachOrientation; + _dragStartWindowOffset = dragStartWindowOffset; + _dragStartItemOffset = dragStartItemOffset; + _itemPositionWithinHeader = itemPositionWithinHeader; + _itemSize = itemSize; + } + + public InterTabTransfer(object item, DragablzItem originatorContainer, Point dragStartItemOffset) + { + if (item == null) throw new ArgumentNullException("item"); + if (originatorContainer == null) throw new ArgumentNullException("originatorContainer"); + + _transferReason = InterTabTransferReason.Reentry; + + _item = item; + _originatorContainer = originatorContainer; + _dragStartItemOffset = dragStartItemOffset; + } + + public Orientation BreachOrientation + { + get { return _breachOrientation; } + } + + public Point DragStartWindowOffset + { + get { return _dragStartWindowOffset; } + } + + public object Item + { + get { return _item; } + } + + public DragablzItem OriginatorContainer + { + get { return _originatorContainer; } + } + + public InterTabTransferReason TransferReason + { + get { return _transferReason; } + } + + public Point DragStartItemOffset + { + get { return _dragStartItemOffset; } + } + + public Point ItemPositionWithinHeader + { + get { return _itemPositionWithinHeader; } + } + + public Size ItemSize + { + get { return _itemSize; } + } + } +} \ No newline at end of file diff --git a/Dragablz/LinearOrganiser.cs b/Dragablz/LinearOrganiser.cs new file mode 100644 index 0000000..6148719 --- /dev/null +++ b/Dragablz/LinearOrganiser.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace Dragablz +{ + public abstract class LinearOrganiser : IItemsOrganiser + { + private readonly Orientation _orientation; + private readonly Func _getDesiredSize; + private readonly Func _getLocation; + private readonly Action _setLocation; + + protected LinearOrganiser(Orientation orientation) + { + _orientation = orientation; + + switch (orientation) + { + case Orientation.Horizontal: + _getDesiredSize = item => item.DesiredSize.Width; + _getLocation = item => item.X; + _setLocation = (item, coord) => item.X = coord; + break; + case Orientation.Vertical: + _getDesiredSize = item => item.DesiredSize.Height; + _getLocation = item => item.Y; + _setLocation = (item, coord) => item.Y = coord; + break; + default: + throw new ArgumentOutOfRangeException("orientation"); + } + } + + #region LocationInfo + + private class LocationInfo + { + private readonly DragablzItem _item; + private readonly double _start; + private readonly double _mid; + private readonly double _end; + + public LocationInfo(DragablzItem item, double start, double mid, double end) + { + _item = item; + _start = start; + _mid = mid; + _end = end; + } + + public double Start + { + get { return _start; } + } + + public double Mid + { + get { return _mid; } + } + + public double End + { + get { return _end; } + } + + public DragablzItem Item + { + get { return _item; } + } + } + + #endregion + + public Orientation Orientation + { + get { return _orientation; } + } + + public void Organise(Size bounds, IEnumerable items) + { + if (items == null) throw new ArgumentNullException("items"); + + var currentCoord = 0.0; + foreach ( + var newItem in + items.Select((di, idx) => new Tuple(idx, di)) + .OrderBy(tuple => tuple, + MultiComparer>.Ascending(tuple => _getLocation(tuple.Item2)) + .ThenAscending(tuple => tuple.Item1)) + .Select(tuple => tuple.Item2)) + { + _setLocation(newItem, currentCoord); + newItem.Measure(bounds); + currentCoord += _getDesiredSize(newItem); + } + } + + + public void OrganiseOnDrag(Size bounds, IEnumerable siblingItems, DragablzItem dragItem) + { + if (siblingItems == null) throw new ArgumentNullException("siblingItems"); + if (dragItem == null) throw new ArgumentNullException("dragItem"); + + var currentLocations = siblingItems + .Select(GetLocationInfo) + .Union(new [] { GetLocationInfo(dragItem)}) + .OrderBy(loc => loc.Start); + + var currentCoord = 0.0; + var zIndex = 0; + foreach (var location in currentLocations) + { + if (!Equals(location.Item, dragItem)) + { + _setLocation(location.Item, currentCoord); + Panel.SetZIndex(location.Item, zIndex++); + } + currentCoord += _getDesiredSize(location.Item); + } + Panel.SetZIndex(dragItem, zIndex); + } + + public void OrganiseOnDragCompleted(Size bounds, IEnumerable siblingItems, DragablzItem dragItem) + { + if (siblingItems == null) throw new ArgumentNullException("siblingItems"); + var currentLocations = siblingItems + .Select(GetLocationInfo) + .Union(new[] { GetLocationInfo(dragItem) }) + .OrderBy(loc => loc.Start); + + var currentCoord = 0.0; + foreach (var location in currentLocations) + { + _setLocation(location.Item, currentCoord); + currentCoord += _getDesiredSize(location.Item); + } + } + + public Point ConstrainLocation(Size bounds, Point desiredLocation, Size itemDesiredSize) + { + return new Point( + _orientation == Orientation.Vertical ? 0 : Math.Min(Math.Max(-1, desiredLocation.X), (bounds.Width) + 1), + _orientation == Orientation.Horizontal ? 0 : Math.Min(Math.Max(-1, desiredLocation.Y), (bounds.Height) + 1) + ); + } + + public Size Measure(IEnumerable items) + { + if (items == null) throw new ArgumentNullException("items"); + + var size = new Size(double.PositiveInfinity, double.PositiveInfinity); + + double width = 0, height = 0; + foreach (var dragablzItem in items) + { + dragablzItem.Measure(size); + if (_orientation == Orientation.Horizontal) + { + width += !dragablzItem.IsLoaded ? dragablzItem.DesiredSize.Width : dragablzItem.ActualWidth; + height = Math.Max(height, !dragablzItem.IsLoaded ? dragablzItem.DesiredSize.Height : dragablzItem.ActualHeight); + } + else + { + width = Math.Max(width, !dragablzItem.IsLoaded ? dragablzItem.DesiredSize.Width : dragablzItem.ActualWidth); + height += !dragablzItem.IsLoaded ? dragablzItem.DesiredSize.Height : dragablzItem.ActualHeight; + } + } + + return new Size(width, height); + } + + private LocationInfo GetLocationInfo(DragablzItem item) + { + var size = _getDesiredSize(item); + var startLocation = _getLocation(item); + var midLocation = startLocation + size / 2; + var endLocation = startLocation + size; + + return new LocationInfo(item, startLocation, midLocation, endLocation); + } + } +} \ No newline at end of file diff --git a/Dragablz/LinearPositionMonitor.cs b/Dragablz/LinearPositionMonitor.cs new file mode 100644 index 0000000..e192ca3 --- /dev/null +++ b/Dragablz/LinearPositionMonitor.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Controls; + +namespace Dragablz +{ + /// + /// A linear position monitor simplifies the montoring of the order of items, where they are laid out + /// horizontally or vertically (typically via a . + /// + public abstract class LinearPositionMonitor : PositionMonitor + { + private readonly Func _getLocation; + + protected LinearPositionMonitor(Orientation orientation) + { + switch (orientation) + { + case Orientation.Horizontal: + _getLocation = item => item.X; + break; + case Orientation.Vertical: + _getLocation = item => item.Y; + break; + default: + throw new ArgumentOutOfRangeException("orientation"); + } + } + + public event EventHandler OrderChanged; + + internal virtual void OnOrderChanged(OrderChangedEventArgs e) + { + var handler = OrderChanged; + if (handler != null) handler(this, e); + } + + internal IEnumerable Sort(IEnumerable items) + { + if (items == null) throw new ArgumentNullException("items"); + + return items.OrderBy(i => _getLocation(i)); + } + } +} \ No newline at end of file diff --git a/Dragablz/LocationChangedEventArgs.cs b/Dragablz/LocationChangedEventArgs.cs new file mode 100644 index 0000000..eba6d1a --- /dev/null +++ b/Dragablz/LocationChangedEventArgs.cs @@ -0,0 +1,29 @@ +using System; +using System.Windows; + +namespace Dragablz +{ + public class LocationChangedEventArgs + { + private readonly object _item; + private readonly Point _location; + + public LocationChangedEventArgs(object item, Point location) + { + if (item == null) throw new ArgumentNullException("item"); + + _item = item; + _location = location; + } + + public object Item + { + get { return _item; } + } + + public Point Location + { + get { return _location; } + } + } +} \ No newline at end of file diff --git a/Dragablz/MultiComparer.cs b/Dragablz/MultiComparer.cs new file mode 100644 index 0000000..29d806f --- /dev/null +++ b/Dragablz/MultiComparer.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Dragablz +{ + internal class MultiComparer : IComparer + { + private readonly IList> _attributeComparers; + + private MultiComparer(FuncComparer firstComparer) + { + _attributeComparers = new List> + { + firstComparer + }; + } + + public static MultiComparer Ascending(Func accessor) + where TAttribute : IComparable + { + if (accessor == null) throw new ArgumentNullException("accessor"); + + return new MultiComparer(BuildAscendingComparer(accessor)); + } + + public static MultiComparer Descending(Func accessor) + where TAttribute : IComparable + { + if (accessor == null) throw new ArgumentNullException("accessor"); + + return new MultiComparer(BuildDescendingComparer(accessor)); + } + + public MultiComparer ThenAscending(Func accessor) + where TAttribute : IComparable + { + if (accessor == null) throw new ArgumentNullException("accessor"); + + _attributeComparers.Add(BuildAscendingComparer(accessor)); + + return this; + } + + public MultiComparer ThenDescending(Func accessor) + where TAttribute : IComparable + { + if (accessor == null) throw new ArgumentNullException("accessor"); + + _attributeComparers.Add(BuildDescendingComparer(accessor)); + + return this; + } + + public int Compare(TObject x, TObject y) + { + var nonEqual = _attributeComparers.Select(c => new {result = c.Compare(x, y)}).FirstOrDefault(a => a.result != 0); + + return nonEqual == null ? 0 : nonEqual.result; + } + + private static FuncComparer BuildAscendingComparer(Func accessor) + where TAttribute : IComparable + { + //TODO handle ref types better + return new FuncComparer((x, y) => accessor(x).CompareTo(accessor(y))); + + } + + private static FuncComparer BuildDescendingComparer(Func accessor) + where TAttribute : IComparable + { + //TODO handle ref types better + return new FuncComparer((x, y) => accessor(y).CompareTo(accessor(x))); + } + } +} diff --git a/Dragablz/Native.cs b/Dragablz/Native.cs new file mode 100644 index 0000000..a7ff773 --- /dev/null +++ b/Dragablz/Native.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Interop; + +namespace Dragablz +{ + internal static class Native + { + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public int X; + public int Y; + + public static implicit operator Point(POINT point) + { + return new Point(point.X, point.Y); + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct RECT + { + public int left; + public int top; + public int right; + public int bottom; + } + + [DllImport("user32.dll")] + private static extern bool GetCursorPos(out POINT lpPoint); + + public static Point GetCursorPos() + { + POINT lpPoint; + GetCursorPos(out lpPoint); + return lpPoint; + } + + public static IEnumerable SortWindowsTopToBottom(IEnumerable windows) + { + var windowsByHandle = windows.Select(window => + { + var hwndSource = PresentationSource.FromVisual(window) as HwndSource; + var handle = hwndSource != null ? hwndSource.Handle : IntPtr.Zero; + return new {window, handle}; + }).Where(x => x.handle != IntPtr.Zero) + .ToDictionary(x => x.handle, x => x.window); + + for (var hWnd = GetTopWindow(IntPtr.Zero); hWnd != IntPtr.Zero; hWnd = GetWindow(hWnd, GW_HWNDNEXT)) + if (windowsByHandle.ContainsKey((hWnd))) + yield return windowsByHandle[hWnd]; + } + + public const int SW_SHOWNORMAL = 1; + [DllImport("user32.dll")] + public static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl); + + private const uint GW_HWNDNEXT = 2; + [DllImport("User32")] + public static extern IntPtr GetTopWindow(IntPtr hWnd); + + [DllImport("User32")] + public static extern IntPtr GetWindow(IntPtr hWnd, uint wCmd); + + [Serializable] + [StructLayout(LayoutKind.Sequential)] + public struct WINDOWPLACEMENT + { + public int length; + public int flags; + public int showCmd; + public POINT minPosition; + public POINT maxPosition; + public RECT normalPosition; + } + } +} diff --git a/Dragablz/NewTabHost.cs b/Dragablz/NewTabHost.cs new file mode 100644 index 0000000..3e7392b --- /dev/null +++ b/Dragablz/NewTabHost.cs @@ -0,0 +1,30 @@ +using System; +using System.Windows; + +namespace Dragablz +{ + public class NewTabHost : INewTabHost + { + private readonly Window _window; + private readonly TabablzControl _tabablzControl; + + public NewTabHost(Window window, TabablzControl tabablzControl) + { + if (window == null) throw new ArgumentNullException("window"); + if (tabablzControl == null) throw new ArgumentNullException("tabablzControl"); + + _window = window; + _tabablzControl = tabablzControl; + } + + public Window Window + { + get { return _window; } + } + + public TabablzControl TabablzControl + { + get { return _tabablzControl; } + } + } +} \ No newline at end of file diff --git a/Dragablz/OrderChangedEventArgs.cs b/Dragablz/OrderChangedEventArgs.cs new file mode 100644 index 0000000..1bf7f01 --- /dev/null +++ b/Dragablz/OrderChangedEventArgs.cs @@ -0,0 +1,28 @@ +using System; + +namespace Dragablz +{ + public class OrderChangedEventArgs + { + private readonly object[] _previousOrder; + private readonly object[] _newOrder; + + public OrderChangedEventArgs(object[] previousOrder, object[] newOrder) + { + if (newOrder == null) throw new ArgumentNullException("newOrder"); + + _previousOrder = previousOrder; + _newOrder = newOrder; + } + + public object[] PreviousOrder + { + get { return _previousOrder; } + } + + public object[] NewOrder + { + get { return _newOrder; } + } + } +} \ No newline at end of file diff --git a/Dragablz/PositionMonitor.cs b/Dragablz/PositionMonitor.cs new file mode 100644 index 0000000..f866440 --- /dev/null +++ b/Dragablz/PositionMonitor.cs @@ -0,0 +1,28 @@ +using System; +using System.Text; +using System.Threading.Tasks; + +namespace Dragablz +{ + /// + /// Consumers can provide a position monitor to receive updates regarding the location of an item. + /// + /// + /// A can be used to listen to changes + /// instead of routed events, which can be easier in a MVVM scenario. + /// + public class PositionMonitor + { + public event EventHandler LocationChanged; + + internal virtual void OnLocationChanged(LocationChangedEventArgs e) + { + if (e == null) throw new ArgumentNullException("e"); + + var handler = LocationChanged; + if (handler != null) handler(this, e); + } + + internal virtual void ItemsChanged() { } + } +} diff --git a/Dragablz/Properties/AssemblyInfo.cs b/Dragablz/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..fe834aa --- /dev/null +++ b/Dragablz/Properties/AssemblyInfo.cs @@ -0,0 +1,57 @@ +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("Dragablz")] +[assembly: AssemblyDescription("Dragable tabable")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Mulholland Software Ltd/James Willock")] +[assembly: AssemblyProduct("Dragablz")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 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")] + +[assembly: InternalsVisibleTo("Dragablz.Test")] \ No newline at end of file diff --git a/Dragablz/Properties/Resources.Designer.cs b/Dragablz/Properties/Resources.Designer.cs new file mode 100644 index 0000000..eb1fe14 --- /dev/null +++ b/Dragablz/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34014 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Dragablz.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Dragablz.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Dragablz/Properties/Resources.resx b/Dragablz/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/Dragablz/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Dragablz/Properties/Settings.Designer.cs b/Dragablz/Properties/Settings.Designer.cs new file mode 100644 index 0000000..af46672 --- /dev/null +++ b/Dragablz/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34014 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Dragablz.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Dragablz/Properties/Settings.settings b/Dragablz/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/Dragablz/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Dragablz/Referenceless/AnonymousDisposable.cs b/Dragablz/Referenceless/AnonymousDisposable.cs new file mode 100644 index 0000000..44a96dd --- /dev/null +++ b/Dragablz/Referenceless/AnonymousDisposable.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading; + +namespace Dragablz.Referenceless +{ + internal sealed class AnonymousDisposable : ICancelable, IDisposable + { + private volatile Action _dispose; + + public bool IsDisposed + { + get + { + return this._dispose == null; + } + } + + public AnonymousDisposable(Action dispose) + { + this._dispose = dispose; + } + + public void Dispose() + { + var action = Interlocked.Exchange(ref _dispose, (Action)null); + if (action == null) + return; + action(); + } + } +} diff --git a/Dragablz/Referenceless/DefaultDisposable.cs b/Dragablz/Referenceless/DefaultDisposable.cs new file mode 100644 index 0000000..9cb5703 --- /dev/null +++ b/Dragablz/Referenceless/DefaultDisposable.cs @@ -0,0 +1,21 @@ +using System; + +namespace Dragablz.Referenceless +{ + internal sealed class DefaultDisposable : IDisposable + { + public static readonly DefaultDisposable Instance = new DefaultDisposable(); + + static DefaultDisposable() + { + } + + private DefaultDisposable() + { + } + + public void Dispose() + { + } + } +} diff --git a/Dragablz/Referenceless/Disposable.cs b/Dragablz/Referenceless/Disposable.cs new file mode 100644 index 0000000..75752b9 --- /dev/null +++ b/Dragablz/Referenceless/Disposable.cs @@ -0,0 +1,23 @@ +using System; + +namespace Dragablz.Referenceless +{ + internal static class Disposable + { + public static IDisposable Empty + { + get + { + return (IDisposable)DefaultDisposable.Instance; + } + } + + public static IDisposable Create(Action dispose) + { + if (dispose == null) + throw new ArgumentNullException("dispose"); + else + return (IDisposable)new AnonymousDisposable(dispose); + } + } +} diff --git a/Dragablz/Referenceless/ICancelable.cs b/Dragablz/Referenceless/ICancelable.cs new file mode 100644 index 0000000..350b1ba --- /dev/null +++ b/Dragablz/Referenceless/ICancelable.cs @@ -0,0 +1,9 @@ +using System; + +namespace Dragablz.Referenceless +{ + internal interface ICancelable : IDisposable + { + bool IsDisposed { get; } + } +} diff --git a/Dragablz/TabEmptiedResponse.cs b/Dragablz/TabEmptiedResponse.cs new file mode 100644 index 0000000..338725c --- /dev/null +++ b/Dragablz/TabEmptiedResponse.cs @@ -0,0 +1,14 @@ +namespace Dragablz +{ + public enum TabEmptiedResponse + { + /// + /// Allow the Window to be closed automatically. + /// + CloseWindow, + /// + /// The window will not be closed by the , probably meaning the implementor will close the window manually + /// + DoNothing + } +} \ No newline at end of file diff --git a/Dragablz/TabHeaderDragStartInformation.cs b/Dragablz/TabHeaderDragStartInformation.cs new file mode 100644 index 0000000..478eac2 --- /dev/null +++ b/Dragablz/TabHeaderDragStartInformation.cs @@ -0,0 +1,51 @@ +using System; + +namespace Dragablz +{ + internal class TabHeaderDragStartInformation + { + private readonly DragablzItem _dragItem; + private readonly double _dragablzItemsControlHorizontalOffset; + private readonly double _dragablzItemControlVerticalOffset; + private readonly double _dragablzItemHorizontalOffset; + private readonly double _dragablzItemVerticalOffset; + + public TabHeaderDragStartInformation( + DragablzItem dragItem, + double dragablzItemsControlHorizontalOffset, double dragablzItemControlVerticalOffset, double dragablzItemHorizontalOffset, double dragablzItemVerticalOffset) + { + if (dragItem == null) throw new ArgumentNullException("dragItem"); + + _dragItem = dragItem; + _dragablzItemsControlHorizontalOffset = dragablzItemsControlHorizontalOffset; + _dragablzItemControlVerticalOffset = dragablzItemControlVerticalOffset; + _dragablzItemHorizontalOffset = dragablzItemHorizontalOffset; + _dragablzItemVerticalOffset = dragablzItemVerticalOffset; + } + + public double DragablzItemsControlHorizontalOffset + { + get { return _dragablzItemsControlHorizontalOffset; } + } + + public double DragablzItemControlVerticalOffset + { + get { return _dragablzItemControlVerticalOffset; } + } + + public double DragablzItemHorizontalOffset + { + get { return _dragablzItemHorizontalOffset; } + } + + public double DragablzItemVerticalOffset + { + get { return _dragablzItemVerticalOffset; } + } + + public DragablzItem DragItem + { + get { return _dragItem; } + } + } +} \ No newline at end of file diff --git a/Dragablz/TabablzControl.cs b/Dragablz/TabablzControl.cs new file mode 100644 index 0000000..5f8ca75 --- /dev/null +++ b/Dragablz/TabablzControl.cs @@ -0,0 +1,651 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using Dragablz.Referenceless; + +namespace Dragablz +{ + //original code specific to keeping visual tree "alive" sourced from http://stackoverflow.com/questions/12432062/binding-to-itemssource-of-tabcontrol-in-wpf + + /// + /// Extended tab control which supports tab repositioning, and drag and drop. Also + /// uses the common WPF technique for pesisting the visual tree across tabs. + /// + [TemplatePart(Name = HeaderItemsControlPartName, Type = typeof(DragablzItemsControl))] + [TemplatePart(Name = ItemsHolderPartName, Type = typeof(Panel))] + public class TabablzControl : TabControl + { + public const string HeaderItemsControlPartName = "PART_HeaderItemsControl"; + public const string ItemsHolderPartName = "PART_ItemsHolder"; + + private static readonly HashSet _loadedInstances = new HashSet(); + + private Panel _itemsHolder; + private TabHeaderDragStartInformation _tabHeaderDragStartInformation; + private object _previousSelection; + private DragablzItemsControl _dragablzItemsControl; + private IDisposable _templateSubscription; + + static TabablzControl() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(TabablzControl), new FrameworkPropertyMetadata(typeof(TabablzControl))); + } + + public TabablzControl() + { + AddHandler(DragablzItem.DragStarted, new DragablzDragStartedEventHandler(ItemDragStarted), true); + AddHandler(DragablzItem.PreviewDragDelta, new DragablzDragDeltaEventHandler(PreviewItemDragDelta), true); + AddHandler(DragablzItem.DragDelta, new DragablzDragDeltaEventHandler(ItemDragDelta), true); + AddHandler(DragablzItem.DragCompleted, new DragablzDragCompletedEventHandler(ItemDragCompleted), true); + + Loaded += (sender, args) => _loadedInstances.Add(this); + Unloaded += (sender, args) => _loadedInstances.Remove(this); + } + + private static readonly DependencyProperty IsItemSelectedProperty = DependencyProperty.RegisterAttached( + "IsItemSelected", typeof (bool), typeof (TabablzControl), new FrameworkPropertyMetadata(default(bool), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)); + + internal static void SetIsItemSelected(DependencyObject element, bool value) + { + element.SetCurrentValue(IsItemSelectedProperty, value); + } + + public static bool GetIsItemSelected(DependencyObject element) + { + return (bool) element.GetValue(IsItemSelectedProperty); + } + + public static readonly DependencyProperty CustomHeaderItemStyleProperty = DependencyProperty.Register( + "CustomHeaderItemStyle", typeof (Style), typeof (TabablzControl), new PropertyMetadata(default(Style))); + + /// + /// Style to apply to header items which are not their own item container (). Typically items bound via the will use this style. + /// + public Style CustomHeaderItemStyle + { + get { return (Style) GetValue(CustomHeaderItemStyleProperty); } + set { SetValue(CustomHeaderItemStyleProperty, value); } + } + + public static readonly DependencyProperty CustomHeaderItemTemplateProperty = DependencyProperty.Register( + "CustomHeaderItemTemplate", typeof (DataTemplate), typeof (TabablzControl), new PropertyMetadata(default(DataTemplate))); + + public DataTemplate CustomHeaderItemTemplate + { + get { return (DataTemplate) GetValue(CustomHeaderItemTemplateProperty); } + set { SetValue(CustomHeaderItemTemplateProperty, value); } + } + + public static readonly DependencyProperty DefaultHeaderItemStyleProperty = DependencyProperty.Register( + "DefaultHeaderItemStyle", typeof (Style), typeof (TabablzControl), new PropertyMetadata(default(Style))); + + public Style DefaultHeaderItemStyle + { + get { return (Style) GetValue(DefaultHeaderItemStyleProperty); } + set { SetValue(DefaultHeaderItemStyleProperty, value); } + } + + public static readonly DependencyProperty HeaderPrefixContentProperty = DependencyProperty.Register( + "HeaderPrefixContent", typeof (object), typeof (TabablzControl), new PropertyMetadata(default(object))); + + public object HeaderPrefixContent + { + get { return (object) GetValue(HeaderPrefixContentProperty); } + set { SetValue(HeaderPrefixContentProperty, value); } + } + + public static readonly DependencyProperty HeaderPrefixContentStringFormatProperty = DependencyProperty.Register( + "HeaderPrefixContentStringFormat", typeof (string), typeof (TabablzControl), new PropertyMetadata(default(string))); + + public string HeaderPrefixContentStringFormat + { + get { return (string) GetValue(HeaderPrefixContentStringFormatProperty); } + set { SetValue(HeaderPrefixContentStringFormatProperty, value); } + } + + public static readonly DependencyProperty HeaderPrefixContentTemplateProperty = DependencyProperty.Register( + "HeaderPrefixContentTemplate", typeof (DataTemplate), typeof (TabablzControl), new PropertyMetadata(default(DataTemplate))); + + public DataTemplate HeaderPrefixContentTemplate + { + get { return (DataTemplate) GetValue(HeaderPrefixContentTemplateProperty); } + set { SetValue(HeaderPrefixContentTemplateProperty, value); } + } + + public static readonly DependencyProperty HeaderPrefixContentTemplateSelectorProperty = DependencyProperty.Register( + "HeaderPrefixContentTemplateSelector", typeof (DataTemplateSelector), typeof (TabablzControl), new PropertyMetadata(default(DataTemplateSelector))); + + public DataTemplateSelector HeaderPrefixContentTemplateSelector + { + get { return (DataTemplateSelector) GetValue(HeaderPrefixContentTemplateSelectorProperty); } + set { SetValue(HeaderPrefixContentTemplateSelectorProperty, value); } + } + + public static readonly DependencyProperty HeaderSuffixContentProperty = DependencyProperty.Register( + "HeaderSuffixContent", typeof(object), typeof(TabablzControl), new PropertyMetadata(default(object))); + + public object HeaderSuffixContent + { + get { return (object)GetValue(HeaderSuffixContentProperty); } + set { SetValue(HeaderSuffixContentProperty, value); } + } + + public static readonly DependencyProperty HeaderSuffixContentStringFormatProperty = DependencyProperty.Register( + "HeaderSuffixContentStringFormat", typeof(string), typeof(TabablzControl), new PropertyMetadata(default(string))); + + public string HeaderSuffixContentStringFormat + { + get { return (string)GetValue(HeaderSuffixContentStringFormatProperty); } + set { SetValue(HeaderSuffixContentStringFormatProperty, value); } + } + + public static readonly DependencyProperty HeaderSuffixContentTemplateProperty = DependencyProperty.Register( + "HeaderSuffixContentTemplate", typeof(DataTemplate), typeof(TabablzControl), new PropertyMetadata(default(DataTemplate))); + + public DataTemplate HeaderSuffixContentTemplate + { + get { return (DataTemplate)GetValue(HeaderSuffixContentTemplateProperty); } + set { SetValue(HeaderSuffixContentTemplateProperty, value); } + } + + public static readonly DependencyProperty HeaderSuffixContentTemplateSelectorProperty = DependencyProperty.Register( + "HeaderSuffixContentTemplateSelector", typeof(DataTemplateSelector), typeof(TabablzControl), new PropertyMetadata(default(DataTemplateSelector))); + + public DataTemplateSelector HeaderSuffixContentTemplateSelector + { + get { return (DataTemplateSelector)GetValue(HeaderSuffixContentTemplateSelectorProperty); } + set { SetValue(HeaderSuffixContentTemplateSelectorProperty, value); } + } + + public static readonly DependencyProperty InterTabControllerProperty = DependencyProperty.Register( + "InterTabController", typeof (InterTabController), typeof (TabablzControl), new PropertyMetadata(null, InterTabControllerPropertyChangedCallback)); + + private static void InterTabControllerPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) + { + var instance = (TabablzControl)dependencyObject; + if (dependencyPropertyChangedEventArgs.OldValue != null) + instance.RemoveLogicalChild(dependencyPropertyChangedEventArgs.OldValue); + if (dependencyPropertyChangedEventArgs.NewValue != null) + instance.AddLogicalChild(dependencyPropertyChangedEventArgs.NewValue); + } + + public InterTabController InterTabController + { + get { return (InterTabController) GetValue(InterTabControllerProperty); } + set { SetValue(InterTabControllerProperty, value); } + } + + public override void OnApplyTemplate() + { + if (_templateSubscription != null) + _templateSubscription.Dispose(); + _templateSubscription = Disposable.Empty; + + _dragablzItemsControl = GetTemplateChild(HeaderItemsControlPartName) as DragablzItemsControl; + if (_dragablzItemsControl != null) + { + _dragablzItemsControl.ItemContainerGenerator.StatusChanged += ItemContainerGeneratorOnStatusChanged; + _templateSubscription = + Disposable.Create( + () => + _dragablzItemsControl.ItemContainerGenerator.StatusChanged -= + ItemContainerGeneratorOnStatusChanged); + if (_dragablzItemsControl.ItemContainerStyleSelector == null) + _dragablzItemsControl.ItemContainerStyleSelector = new TabablzItemStyleSelector(DefaultHeaderItemStyle, + CustomHeaderItemStyle); + } + + _itemsHolder = GetTemplateChild(ItemsHolderPartName) as Panel; + UpdateSelectedItem(); + MarkInitialSelection(); + + base.OnApplyTemplate(); + } + + /// + /// update the visible child in the ItemsHolder + /// + /// + protected override void OnSelectionChanged(SelectionChangedEventArgs e) + { + if (e.RemovedItems.Count > 0) + _previousSelection = e.RemovedItems[0]; + else if (e.AddedItems.Count > 0) + _previousSelection = e.AddedItems[0]; + else + _previousSelection = null; + + base.OnSelectionChanged(e); + UpdateSelectedItem(); + + if (_dragablzItemsControl == null) return; + + Func> notTabItems = + l => + l.Cast() + .Where(o => !(o is TabItem)) + .Select(o => _dragablzItemsControl.ItemContainerGenerator.ContainerFromItem(o)) + .OfType(); + + foreach (var addedItem in notTabItems(e.AddedItems)) + { + SetIsItemSelected(addedItem, true); + addedItem.BringIntoView(); + } + + foreach (var removedItem in notTabItems(e.RemovedItems)) + { + SetIsItemSelected(removedItem, false); + } + } + + /// + /// when the items change we remove any generated panel children and add any new ones as necessary + /// + /// + protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) + { + base.OnItemsChanged(e); + + if (_itemsHolder == null) + { + return; + } + + switch (e.Action) + { + case NotifyCollectionChangedAction.Reset: + _itemsHolder.Children.Clear(); + + if (Items.Count > 0) + { + SelectedItem = base.Items[0]; + UpdateSelectedItem(); + } + + break; + + case NotifyCollectionChangedAction.Add: + UpdateSelectedItem(); + break; + + case NotifyCollectionChangedAction.Remove: + + if (e.OldItems.Contains(SelectedItem)) + System.Diagnostics.Debugger.Break(); + + foreach (var item in e.OldItems) + { + var cp = FindChildContentPresenter(item); + if (cp != null) + _itemsHolder.Children.Remove(cp); + } + + UpdateSelectedItem(); + break; + + case NotifyCollectionChangedAction.Replace: + throw new NotImplementedException("Replace not implemented yet"); + } + } + + private void MarkInitialSelection() + { + if (_dragablzItemsControl == null || + _dragablzItemsControl.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated) return; + + if (_dragablzItemsControl != null && SelectedItem != null && !(SelectedItem is TabItem)) + { + var containerFromItem = _dragablzItemsControl.ItemContainerGenerator.ContainerFromItem(SelectedItem); + if (containerFromItem != null) + SetIsItemSelected(_dragablzItemsControl.ItemContainerGenerator.ContainerFromItem(SelectedItem), true); + } + } + + private void ItemDragStarted(object sender, DragablzDragStartedEventArgs e) + { + //the thumb may steal the user selection, so we will try and apply it manually + if (_dragablzItemsControl == null) return; + + var sourceOfDragItemsControl = ItemsControlFromItemContainer(e.DragablzItem) as DragablzItemsControl; + if (sourceOfDragItemsControl != null && Equals(sourceOfDragItemsControl, _dragablzItemsControl)) + { + var itemsControlOffset = Mouse.GetPosition(_dragablzItemsControl); + _tabHeaderDragStartInformation = new TabHeaderDragStartInformation(e.DragablzItem, itemsControlOffset.X, + itemsControlOffset.Y, e.DragStartedEventArgs.HorizontalOffset, e.DragStartedEventArgs.VerticalOffset); + + foreach (var otherItem in _dragablzItemsControl.Containers().Except(e.DragablzItem)) + SetIsItemSelected(otherItem, false); + SetIsItemSelected(e.DragablzItem, true); + var item = _dragablzItemsControl.ItemContainerGenerator.ItemFromContainer(e.DragablzItem); + var tabItem = item as TabItem; + if (tabItem != null) + tabItem.IsSelected = true; + else + SelectedItem = item; + } + } + + private void PreviewItemDragDelta(object sender, DragablzDragDeltaEventArgs e) + { + if (_dragablzItemsControl == null) return; + + var sourceOfDragItemsControl = ItemsControlFromItemContainer(e.DragablzItem) as DragablzItemsControl; + if (sourceOfDragItemsControl != null && Equals(sourceOfDragItemsControl, _dragablzItemsControl)) + { + if (Items.Count == 1 && (InterTabController == null || InterTabController.MoveWindowWithSolitaryTabs)) + { + if (MonitorReentry(e)) return; + + var myWindow = Window.GetWindow(this); + if (myWindow == null) return; + + if (_interTabTransfer != null) + { + var cursorPos = Native.GetCursorPos(); + + if (_interTabTransfer.BreachOrientation == Orientation.Vertical) + { + var vector = cursorPos - _interTabTransfer.DragStartWindowOffset; + myWindow.Left = vector.X; + myWindow.Top = vector.Y; + } + else + { + var offset = e.DragablzItem.TranslatePoint(_interTabTransfer.OriginatorContainer.MouseAtDragStart, myWindow); + var borderVector = myWindow.PointToScreen(new Point()) - new Point(myWindow.Left, myWindow.Top); + offset.Offset(borderVector.X, borderVector.Y); + myWindow.Left = cursorPos.X - offset.X; + myWindow.Top = cursorPos.Y - offset.Y; + } + } + else + { + myWindow.Left += e.DragDeltaEventArgs.HorizontalChange; + myWindow.Top += e.DragDeltaEventArgs.VerticalChange; + } + + e.Handled = true; + } + } + } + + private bool MonitorReentry(DragablzDragDeltaEventArgs e) + { + var screenMousePosition = _dragablzItemsControl.PointToScreen(Mouse.GetPosition(_dragablzItemsControl)); + + var otherTabablzControls = _loadedInstances + .Where( + tc => + tc != this && tc.InterTabController != null && + Equals(tc.InterTabController.Partition, InterTabController.Partition)) + .Select(tc => + { + var topLeft = tc._dragablzItemsControl.PointToScreen(new Point()); + var bottomRight = + tc._dragablzItemsControl.PointToScreen(new Point(tc._dragablzItemsControl.ActualWidth, + tc._dragablzItemsControl.ActualHeight)); + + return new {tc, topLeft, bottomRight}; + }); + + + var target = Native.SortWindowsTopToBottom(Application.Current.Windows.OfType()) + .Join(otherTabablzControls, w => w, a => Window.GetWindow(a.tc), (w, a) => a) + .FirstOrDefault(a => new Rect(a.topLeft, a.bottomRight).Contains(screenMousePosition)); + + if (target != null) + { + var item = _dragablzItemsControl.ItemContainerGenerator.ItemFromContainer(e.DragablzItem); + + var interTabTransfer = new InterTabTransfer(item, e.DragablzItem, Mouse.GetPosition(e.DragablzItem)); + var contentPresenter = FindChildContentPresenter(item); + RemoveFromSource(item); + _itemsHolder.Children.Remove(contentPresenter); + + e.DragablzItem.IsDragging = false; + var window = Window.GetWindow(this); + if (window != null && InterTabController.InterTabClient.TabEmptiedHandler(this, window) == TabEmptiedResponse.CloseWindow) + window.Close(); + + target.tc.ReceiveDrag(interTabTransfer); + e.Cancel = true; + + return true; + } + + return false; + } + + + private void ItemDragCompleted(object sender, DragablzDragCompletedEventArgs e) + { + _interTabTransfer = null; + _dragablzItemsControl.LockedMeasure = null; + } + + private void ItemDragDelta(object sender, DragablzDragDeltaEventArgs e) + { + if (_tabHeaderDragStartInformation != null && + Equals(_tabHeaderDragStartInformation.DragItem, e.DragablzItem) && + InterTabController != null) + { + if (InterTabController.InterTabClient == null) + throw new InvalidOperationException("An InterTabClient must be provided on an InterTabController."); + + MonitorBreach(e); + } + } + + private void MonitorBreach(DragablzDragDeltaEventArgs e) + { + var mousePosition = Mouse.GetPosition(_dragablzItemsControl); + + Orientation? breachOrientation = null; + if (mousePosition.X < -InterTabController.HorizontalPopoutGrace + || (mousePosition.X - _dragablzItemsControl.ActualWidth) > InterTabController.HorizontalPopoutGrace) + breachOrientation = Orientation.Horizontal; + else if (mousePosition.Y < -InterTabController.VerticalPopoutGrace + || (mousePosition.Y - _dragablzItemsControl.ActualHeight) > InterTabController.VerticalPopoutGrace) + breachOrientation = Orientation.Vertical; + + if (breachOrientation.HasValue) + { + var newTabHost = InterTabController.InterTabClient.GetNewHost(InterTabController.InterTabClient, + InterTabController.Partition); + if (newTabHost == null || newTabHost.TabablzControl == null || newTabHost.Window == null) + throw new ApplicationException("New tab host was not correctly provided"); + + var item = _dragablzItemsControl.ItemContainerGenerator.ItemFromContainer(e.DragablzItem); + + var myWindow = Window.GetWindow(this); + if (myWindow == null) throw new ApplicationException("Unable to find owning window."); + newTabHost.Window.Width = myWindow.RestoreBounds.Width; + newTabHost.Window.Height = myWindow.RestoreBounds.Height; + + var dragStartWindowOffset = e.DragablzItem.TranslatePoint(new Point(), myWindow); + dragStartWindowOffset.Offset(e.DragablzItem.MouseAtDragStart.X, e.DragablzItem.MouseAtDragStart.Y); + var borderVector = myWindow.PointToScreen(new Point()) - new Point(myWindow.Left, myWindow.Top); + dragStartWindowOffset.Offset(borderVector.X, borderVector.Y); + + var dragableItemHeaderPoint = e.DragablzItem.TranslatePoint(new Point(), _dragablzItemsControl); + var dragableItemSize = new Size(e.DragablzItem.ActualWidth, e.DragablzItem.ActualHeight); + + var interTabTransfer = new InterTabTransfer(item, e.DragablzItem, breachOrientation.Value, dragStartWindowOffset, e.DragablzItem.MouseAtDragStart, dragableItemHeaderPoint, dragableItemSize); + + newTabHost.Window.Left = myWindow.Left; + newTabHost.Window.Top = myWindow.Top; + newTabHost.Window.Show(); + var contentPresenter = FindChildContentPresenter(item); + RemoveFromSource(item); + _itemsHolder.Children.Remove(contentPresenter); + + if (_previousSelection != null && Items.Contains(_previousSelection)) + SelectedItem = _previousSelection; + else + SelectedItem = Items.OfType().FirstOrDefault(); + + foreach (var dragablzItem in _dragablzItemsControl.DragablzItems()) + { + dragablzItem.IsDragging = false; + dragablzItem.IsSiblingDragging = false; + } + + newTabHost.TabablzControl.ReceiveDrag(interTabTransfer); + e.Cancel = true; + } + } + + private InterTabTransfer _interTabTransfer; + + internal void ReceiveDrag(InterTabTransfer interTabTransfer) + { + var myWindow = Window.GetWindow(this); + if (myWindow == null) throw new ApplicationException("Unable to find owning window."); + myWindow.Activate(); + + _interTabTransfer = interTabTransfer; + + if (Items.Count == 0) + { + _dragablzItemsControl.LockedMeasure = new Size( + interTabTransfer.ItemPositionWithinHeader.X + interTabTransfer.ItemSize.Width, + interTabTransfer.ItemPositionWithinHeader.Y + interTabTransfer.ItemSize.Height); + } + + AddToSource(interTabTransfer.Item); + + SelectedItem = interTabTransfer.Item; + _dragablzItemsControl.InstigateDrag(interTabTransfer.Item, newContainer => + { + if (interTabTransfer.TransferReason == InterTabTransferReason.Breach) + { + if (interTabTransfer.BreachOrientation == Orientation.Horizontal) + newContainer.Y = interTabTransfer.OriginatorContainer.Y; + else + newContainer.X = interTabTransfer.OriginatorContainer.X; + } + else + { + var mouseXOnItemsControl = Native.GetCursorPos().X - _dragablzItemsControl.PointToScreen(new Point()).X; + newContainer.X = mouseXOnItemsControl - interTabTransfer.DragStartItemOffset.X; + newContainer.Y = 0; + } + newContainer.MouseAtDragStart = interTabTransfer.DragStartItemOffset; + }); + } + + private void AddToSource(object item) + { + var manualInterTabClient = InterTabController.InterTabClient as IManualInterTabClient; + if (manualInterTabClient != null) + { + manualInterTabClient.Add(item); + } + else + { + CollectionTeaser collectionTeaser; + if (CollectionTeaser.TryCreate(ItemsSource, out collectionTeaser)) + collectionTeaser.Add(item); + else + Items.Add(item); + } + } + + private void RemoveFromSource(object item) + { + var manualInterTabClient = InterTabController.InterTabClient as IManualInterTabClient; + if (manualInterTabClient != null) + { + manualInterTabClient.Remove(item); + } + else + { + CollectionTeaser collectionTeaser; + if (CollectionTeaser.TryCreate(ItemsSource, out collectionTeaser)) + collectionTeaser.Remove(item); + else + Items.Remove(item); + } + } + + /// + /// generate a ContentPresenter for the selected item + /// + private void UpdateSelectedItem() + { + if (_itemsHolder == null) + { + return; + } + + CreateChildContentPresenter(SelectedItem); + + // show the right child + var selectedContent = GetContent(SelectedItem); + foreach (ContentPresenter child in _itemsHolder.Children) + { + child.Visibility = child.Content == selectedContent ? Visibility.Visible : Visibility.Collapsed; + } + } + + private static object GetContent(object item) + { + return (item is TabItem) ? (item as TabItem).Content : item; + } + + /// + /// create the child ContentPresenter for the given item (could be data or a TabItem) + /// + /// + /// + private void CreateChildContentPresenter(object item) + { + if (item == null) return; + + var cp = FindChildContentPresenter(item); + if (cp != null) return; + + // the actual child to be added. cp.Tag is a reference to the TabItem + cp = new ContentPresenter + { + Content = GetContent(item), + ContentTemplate = ContentTemplate, + ContentTemplateSelector = ContentTemplateSelector, + ContentStringFormat = ContentStringFormat, + Visibility = Visibility.Collapsed, + }; + _itemsHolder.Children.Add(cp); + } + + /// + /// Find the CP for the given object. data could be a TabItem or a piece of data + /// + /// + /// + private ContentPresenter FindChildContentPresenter(object data) + { + if (data is TabItem) + data = (data as TabItem).Content; + + if (data == null) + return null; + + if (_itemsHolder == null) + return null; + + return _itemsHolder.Children.Cast().FirstOrDefault(cp => cp.Content == data); + } + + private void ItemContainerGeneratorOnStatusChanged(object sender, EventArgs eventArgs) + { + MarkInitialSelection(); + } + } +} diff --git a/Dragablz/TabablzHeaderSizeConverter.cs b/Dragablz/TabablzHeaderSizeConverter.cs new file mode 100644 index 0000000..bd380c7 --- /dev/null +++ b/Dragablz/TabablzHeaderSizeConverter.cs @@ -0,0 +1,50 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Media; + +namespace Dragablz +{ + /// + /// Provides a little help for sizing the header panel in the tab control + /// + public class TabablzHeaderSizeConverter : IMultiValueConverter + { + public Orientation Orientation { get; set; } + + /// + /// The first value should be the total size available size, typically the parent control size. + /// The second value should be from or (height equivalent) + /// All additional values should be siblings sizes (width or height) which will affect (reduce) the available size. + /// + /// + /// + /// + /// + /// + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values == null) throw new ArgumentNullException("values"); + + if (values.Length < 2) return Binding.DoNothing; + + var val = values + .Skip(2) + .OfType() + .Where(d => !double.IsInfinity(d) && !double.IsNaN(d)) + .Aggregate(values.OfType().First(), (current, diminish) => current - diminish); + + var maxWidth = values.Take(2).OfType().Min(); + + return Math.Min(Math.Max(val, 0), maxWidth); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Dragablz/TabablzItemStyleSelector.cs b/Dragablz/TabablzItemStyleSelector.cs new file mode 100644 index 0000000..6085b76 --- /dev/null +++ b/Dragablz/TabablzItemStyleSelector.cs @@ -0,0 +1,27 @@ +using System.Windows; +using System.Windows.Controls; + +namespace Dragablz +{ + /// + /// Selects style to apply to a according to the tab item content itself. + /// + public class TabablzItemStyleSelector : StyleSelector + { + private readonly Style _defaultHeaderItemStyle; + private readonly Style _customHeaderItemStyle; + + public TabablzItemStyleSelector(Style defaultHeaderItemStyle, Style customHeaderItemStyle) + { + _defaultHeaderItemStyle = defaultHeaderItemStyle; + _customHeaderItemStyle = customHeaderItemStyle; + } + + public override Style SelectStyle(object item, DependencyObject container) + { + if (item is TabItem) return _defaultHeaderItemStyle; + + return _customHeaderItemStyle; + } + } +} \ No newline at end of file diff --git a/Dragablz/Themes/Generic.xaml b/Dragablz/Themes/Generic.xaml new file mode 100644 index 0000000..c57427a --- /dev/null +++ b/Dragablz/Themes/Generic.xaml @@ -0,0 +1,760 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Dragablz/Trapezoid.cs b/Dragablz/Trapezoid.cs new file mode 100644 index 0000000..6852295 --- /dev/null +++ b/Dragablz/Trapezoid.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace Dragablz +{ + public class Trapezoid : ContentControl + { + private PathGeometry _pathGeometry; + + static Trapezoid() + { + DefaultStyleKeyProperty.OverrideMetadata(typeof(Trapezoid), new FrameworkPropertyMetadata(typeof(Trapezoid))); + } + + public static readonly DependencyProperty PenBrushProperty = DependencyProperty.Register( + "PenBrush", typeof (Brush), typeof (Trapezoid), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.Transparent), FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public Brush PenBrush + { + get { return (Brush) GetValue(PenBrushProperty); } + set { SetValue(PenBrushProperty, value); } + } + + public static readonly DependencyProperty PenThicknessProperty = DependencyProperty.Register( + "PenThickness", typeof (double), typeof (Trapezoid), new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsMeasure)); + + public double PenThickness + { + get { return (double) GetValue(PenThicknessProperty); } + set { SetValue(PenThicknessProperty, value); } + } + + protected override Size MeasureOverride(Size constraint) + { + var contentDesiredSize = base.MeasureOverride(constraint); + + if (contentDesiredSize.Width == 0 || double.IsInfinity(contentDesiredSize.Width) + || contentDesiredSize.Height == 0 || double.IsInfinity(contentDesiredSize.Height)) + + return contentDesiredSize; + + _pathGeometry = CreateGeometry(contentDesiredSize); + Clip = _pathGeometry; + + return _pathGeometry.GetRenderBounds(CreatePen()).Size; + } + + private Pen CreatePen() + { + return new Pen(PenBrush, PenThickness) + { + EndLineCap = PenLineCap.Flat, + MiterLimit = 1 + }; + } + + private static PathGeometry CreateGeometry(Size contentDesiredSize) + { + //TODO Make better :) do some funky beziers or summit + const double cheapRadiusBig = 6.0; + const double cheapRadiusSmall = cheapRadiusBig/2; + + const int angle = 20; + const double radians = angle * (Math.PI / 180); + + var startPoint = new Point(0, contentDesiredSize.Height + cheapRadiusSmall + cheapRadiusSmall); + + //clockwise starting at bottom left + var bottomLeftSegment = new ArcSegment(new Point(startPoint.X + cheapRadiusSmall, startPoint.Y - cheapRadiusSmall), + new Size(cheapRadiusSmall, cheapRadiusSmall), 315, false, SweepDirection.Counterclockwise, true); + var triangleX = Math.Tan(radians) * (contentDesiredSize.Height); + var leftSegment = new LineSegment(new Point(bottomLeftSegment.Point.X + triangleX, bottomLeftSegment.Point.Y - contentDesiredSize.Height), true); + var topLeftSegment = new ArcSegment(new Point(leftSegment.Point.X + cheapRadiusBig, leftSegment.Point.Y - cheapRadiusSmall), new Size(cheapRadiusBig, cheapRadiusBig), 120, false, SweepDirection.Clockwise, true); + var topSegment = new LineSegment(new Point(contentDesiredSize.Width + cheapRadiusBig + cheapRadiusBig, 0), true); + var topRightSegment = new ArcSegment(new Point(contentDesiredSize.Width + cheapRadiusBig + cheapRadiusBig + cheapRadiusBig, cheapRadiusSmall), new Size(cheapRadiusBig, cheapRadiusBig), 40, false, SweepDirection.Clockwise, true); + + triangleX = Math.Tan(radians) * (contentDesiredSize.Height); + //triangleX = Math.Tan(radians)*(contentDesiredSize.Height - topRightSegment.Point.Y); + var rightSegment = + new LineSegment(new Point(topRightSegment.Point.X + triangleX, + topRightSegment.Point.Y + contentDesiredSize.Height), true); + + var bottomRightSegment = new ArcSegment(new Point(rightSegment.Point.X + cheapRadiusSmall, rightSegment.Point.Y + cheapRadiusSmall), + new Size(cheapRadiusSmall, cheapRadiusSmall), 25, false, SweepDirection.Counterclockwise, true); + var bottomSegment = new LineSegment(new Point(0, bottomRightSegment.Point.Y), true); + + var pathSegmentCollection = new PathSegmentCollection + { + bottomLeftSegment, leftSegment, topLeftSegment, topSegment, topRightSegment, rightSegment, bottomRightSegment, bottomSegment + }; + var pathFigure = new PathFigure(startPoint, pathSegmentCollection, true) + { + IsFilled = true + }; + var pathFigureCollection = new PathFigureCollection + { + pathFigure + }; + + var geometryGroup = new PathGeometry(pathFigureCollection); + geometryGroup.Freeze(); + + return geometryGroup; + } + + protected override void OnRender(DrawingContext drawingContext) + { + base.OnRender(drawingContext); + + drawingContext.DrawGeometry(Background, CreatePen(), _pathGeometry); + } + } +} diff --git a/Dragablz/VerticalOrganiser.cs b/Dragablz/VerticalOrganiser.cs new file mode 100644 index 0000000..9bc629e --- /dev/null +++ b/Dragablz/VerticalOrganiser.cs @@ -0,0 +1,11 @@ +using System.Windows.Controls; + +namespace Dragablz +{ + public class VerticalOrganiser : LinearOrganiser + { + public VerticalOrganiser() : base(Orientation.Vertical) + { + } + } +} \ No newline at end of file diff --git a/Dragablz/VerticalPositionMonitor.cs b/Dragablz/VerticalPositionMonitor.cs new file mode 100644 index 0000000..cb85148 --- /dev/null +++ b/Dragablz/VerticalPositionMonitor.cs @@ -0,0 +1,11 @@ +using System.Windows.Controls; + +namespace Dragablz +{ + public class VerticalPositionMonitor : LinearPositionMonitor + { + public VerticalPositionMonitor() : base(Orientation.Vertical) + { + } + } +} \ No newline at end of file diff --git a/DragablzDemo/AnotherCommandImplementation.cs b/DragablzDemo/AnotherCommandImplementation.cs new file mode 100644 index 0000000..cfc910d --- /dev/null +++ b/DragablzDemo/AnotherCommandImplementation.cs @@ -0,0 +1,53 @@ +using System; +using System.Windows.Input; + +namespace DragablzDemo +{ + /// + /// No WPF project is complete without it's own version of this. + /// + public class AnotherCommandImplementation : ICommand + { + private readonly Action _execute; + private readonly Func _canExecute; + + public AnotherCommandImplementation(Action execute) : this(execute, null) + { + } + + public AnotherCommandImplementation(Action execute, Func canExecute) + { + if (execute == null) throw new ArgumentNullException("execute"); + + _execute = execute; + _canExecute = canExecute ?? (x => true); + } + + public bool CanExecute(object parameter) + { + return _canExecute(parameter); + } + + public void Execute(object parameter) + { + _execute(parameter); + } + + public event EventHandler CanExecuteChanged + { + add + { + CommandManager.RequerySuggested += value; + } + remove + { + CommandManager.RequerySuggested -= value; + } + } + + public void Refresh() + { + CommandManager.InvalidateRequerySuggested(); + } + } +} \ No newline at end of file diff --git a/DragablzDemo/App.xaml b/DragablzDemo/App.xaml new file mode 100644 index 0000000..189ef1c --- /dev/null +++ b/DragablzDemo/App.xaml @@ -0,0 +1,45 @@ + + + + + + + diff --git a/DragablzDemo/App.xaml.cs b/DragablzDemo/App.xaml.cs new file mode 100644 index 0000000..ae0b82b --- /dev/null +++ b/DragablzDemo/App.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows; + +namespace DragablzDemo +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/DragablzDemo/BasicExampleInterTabClient.cs b/DragablzDemo/BasicExampleInterTabClient.cs new file mode 100644 index 0000000..109c3e2 --- /dev/null +++ b/DragablzDemo/BasicExampleInterTabClient.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using Dragablz; + +namespace DragablzDemo +{ + public class BasicExampleInterTabClient : IInterTabClient + { + public INewTabHost GetNewHost(IInterTabClient interTabClient, object partition) + { + var view = new BasicExampleTemplateWindow(); + var model = new BasicExampleTemplateModel(interTabClient, partition); + view.DataContext = model; + return new NewTabHost(view, view.TabablzControl); + } + + public TabEmptiedResponse TabEmptiedHandler(TabablzControl tabControl, Window window) + { + return TabEmptiedResponse.CloseWindow; + } + } +} diff --git a/DragablzDemo/BasicExampleMainModel.cs b/DragablzDemo/BasicExampleMainModel.cs new file mode 100644 index 0000000..428fb4d --- /dev/null +++ b/DragablzDemo/BasicExampleMainModel.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows; +using System.Windows.Input; +using Dragablz; +using DragablzDemo.Annotations; + +namespace DragablzDemo +{ + public class BasicExampleMainModel : INotifyPropertyChanged + { + private readonly ObservableCollection _people = new ObservableCollection(); + private readonly ObservableCollection _viewModels = new ObservableCollection(); + private readonly ICommand _addNewPerson; + private readonly ICommand _addNewViewModel; + private readonly IInterTabClient _interTabClient; + private int _newPersonCount; + private int _newViewModelCount; + private SimpleViewModel _selectedViewModel; + private readonly PositionMonitor _basicColourMonitor; + private string _basicColourMonitorText = "awaiting..."; + private readonly VerticalPositionMonitor _peopleMonitor; + private string _peopleMonitorText = "awaiting..."; + + public BasicExampleMainModel() + { + _basicColourMonitor = new PositionMonitor(); + _basicColourMonitor.LocationChanged += (sender, args) => BasicColourMonitorText = args.Location.ToString(); + _peopleMonitor = new VerticalPositionMonitor(); + _peopleMonitor.OrderChanged += PeopleMonitorOnOrderChanged; + + _people.Add(new Person {FirstName = "Albert", LastName = "Einstein"}); + _people.Add(new Person { FirstName = "Neil", LastName = "Tyson" }); + _people.Add(new Person { FirstName = "James", LastName = "Willock" }); //i move in esteemed circles ;) + + _viewModels.Add(new SimpleViewModel { Name = "Alpha", SimpleContent = "This is the alpha content"}); + _viewModels.Add(new SimpleViewModel { Name = "Beta", SimpleContent = "Beta content", IsSelected = true }); + _viewModels.Add(new SimpleViewModel { Name = "Gamma", SimpleContent = "And here is the gamma content" }); + + SelectedViewModel = _viewModels[1]; + + _interTabClient = new BasicExampleInterTabClient(); + + _addNewPerson = new AnotherCommandImplementation( + x => + { + _newPersonCount++; + _people.Add(new Person + { + FirstName = "Hello_" + _newPersonCount, + LastName = "World_" + _newPersonCount + }); + }); + + _addNewViewModel = new AnotherCommandImplementation( + x => + { + _newViewModelCount++; + _viewModels.Add(new SimpleViewModel() + { + Name = "New Tab " + _newViewModelCount, + SimpleContent = "New Tab Content " + _newViewModelCount + }); + SelectedViewModel = _viewModels.Last(); + }); + } + + public string BasicColourMonitorText + { + get { return _basicColourMonitorText; } + set + { + if (_basicColourMonitorText == value) return; + + _basicColourMonitorText = value; + OnPropertyChanged(); + } + } + + public string PeopleMonitorText + { + get { return _peopleMonitorText; } + set + { + if (_peopleMonitorText == value) return; + + _peopleMonitorText = value; + OnPropertyChanged(); + } + } + + public ReadOnlyObservableCollection People + { + get { return new ReadOnlyObservableCollection(_people); } + } + + public ICommand AddNewPerson + { + get { return _addNewPerson; } + } + + public ObservableCollection ViewModels + { + get { return _viewModels; } + } + + public SimpleViewModel SelectedViewModel + { + get { return _selectedViewModel; } + set + { + if (_selectedViewModel == value) return; + _selectedViewModel = value; + OnPropertyChanged(); + } + } + + public ICommand AddNewViewModel + { + get { return _addNewViewModel; } + } + + public PositionMonitor BasicColourMonitor + { + get { return _basicColourMonitor; } + } + + public PositionMonitor PeopleMonitor + { + get { return _peopleMonitor; } + } + + public IInterTabClient BasicInterTabClient + { + get { return _interTabClient; } + } + + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); + } + + private void PeopleMonitorOnOrderChanged(object sender, OrderChangedEventArgs orderChangedEventArgs) + { + PeopleMonitorText = orderChangedEventArgs.NewOrder.OfType() + .Aggregate("", (accumalate, person) => accumalate + person.LastName + ", "); + } + } + + public class Person + { + public string FirstName { get; set; } + public string LastName { get; set; } + } +} diff --git a/DragablzDemo/BasicExampleMainWindow.xaml b/DragablzDemo/BasicExampleMainWindow.xaml new file mode 100644 index 0000000..fd4870a --- /dev/null +++ b/DragablzDemo/BasicExampleMainWindow.xaml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + Content Defined in XAML, organised horizontally + + + + + Red + Green + Blue + + + + Content Bound From View Model, organised vertically + + + + + + + + + + + + + + + + + + + You can drag tabs around. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Just an empty tab to illustrate you can drag tabs around. + Trapezium/Trapezoid...pretty useful for tabs + + + + + + + + diff --git a/DragablzDemo/BasicExampleMainWindow.xaml.cs b/DragablzDemo/BasicExampleMainWindow.xaml.cs new file mode 100644 index 0000000..6626f68 --- /dev/null +++ b/DragablzDemo/BasicExampleMainWindow.xaml.cs @@ -0,0 +1,17 @@ +using System.Windows; + +namespace DragablzDemo +{ + /// + /// Interaction logic for BasicExampleMainWindow.xaml + /// + public partial class BasicExampleMainWindow : Window + { + public BasicExampleMainWindow() + { + InitializeComponent(); + + DataContext = new BasicExampleMainModel(); + } + } +} diff --git a/DragablzDemo/BasicExampleTemplateModel.cs b/DragablzDemo/BasicExampleTemplateModel.cs new file mode 100644 index 0000000..bef98af --- /dev/null +++ b/DragablzDemo/BasicExampleTemplateModel.cs @@ -0,0 +1,27 @@ +using Dragablz; + +namespace DragablzDemo +{ + public class BasicExampleTemplateModel + { + private readonly IInterTabClient _interTabClient; + private readonly object _partition; + + public BasicExampleTemplateModel(IInterTabClient interTabClient, object partition) + { + _interTabClient = interTabClient; + _partition = partition; + } + + public IInterTabClient InterTabClient + { + get { return _interTabClient; } + } + + public object Partition + { + get { return _partition; } + } + + } +} \ No newline at end of file diff --git a/DragablzDemo/BasicExampleTemplateWindow.xaml b/DragablzDemo/BasicExampleTemplateWindow.xaml new file mode 100644 index 0000000..5bbd7fb --- /dev/null +++ b/DragablzDemo/BasicExampleTemplateWindow.xaml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/DragablzDemo/BasicExampleTemplateWindow.xaml.cs b/DragablzDemo/BasicExampleTemplateWindow.xaml.cs new file mode 100644 index 0000000..2a75177 --- /dev/null +++ b/DragablzDemo/BasicExampleTemplateWindow.xaml.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; + +namespace DragablzDemo +{ + /// + /// Interaction logic for BasicExampleTemplateWindow.xaml + /// + public partial class BasicExampleTemplateWindow : Window + { + public BasicExampleTemplateWindow() + { + InitializeComponent(); + } + } +} diff --git a/DragablzDemo/Boot.cs b/DragablzDemo/Boot.cs new file mode 100644 index 0000000..516bc25 --- /dev/null +++ b/DragablzDemo/Boot.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls.Primitives; +using System.Windows.Interop; +using Dragablz; + +namespace DragablzDemo +{ + public class Boot + { + [STAThread] + public static void Main(string[] args) + { + var app = new App {ShutdownMode = ShutdownMode.OnLastWindowClose}; + app.InitializeComponent(); + + new BasicExampleMainWindow + { + DataContext = new BasicExampleMainModel() + }.Show(); + + var boundExampleModel = new BoundExampleModel( + new SimpleViewModel { Name = "Mon", SimpleContent = "Monday" }, + new SimpleViewModel { Name = "Tues", SimpleContent = "Tuesday" }, + new SimpleViewModel { Name = "Wed", SimpleContent = "Wednesday" }, + new SimpleViewModel { Name = "Thu", SimpleContent = "Thursday" }, + new SimpleViewModel { Name = "Fri", SimpleContent = "Friday" }, + new SimpleViewModel { Name = "Sat", SimpleContent = "Sunday" }, + new SimpleViewModel { Name = "Sun", SimpleContent = "Sunday" } + ); + + app.Resources.Add(SystemParameters.ClientAreaAnimationKey, null); + app.Resources.Add(SystemParameters.MinimizeAnimationKey, null); + app.Resources.Add(SystemParameters.UIEffectsKey, null); + + new BoundExampleWindow() + { + DataContext = boundExampleModel + }.Show(); + + app.Run(); + } + } + + +} diff --git a/DragablzDemo/BoundExampleInterTabClient.cs b/DragablzDemo/BoundExampleInterTabClient.cs new file mode 100644 index 0000000..b59d9ed --- /dev/null +++ b/DragablzDemo/BoundExampleInterTabClient.cs @@ -0,0 +1,21 @@ +using System.Windows; +using Dragablz; + +namespace DragablzDemo +{ + public class BoundExampleInterTabClient : IInterTabClient + { + public INewTabHost GetNewHost(IInterTabClient interTabClient, object partition) + { + var view = new BoundExampleWindow(); + var model = new BoundExampleModel(); + view.DataContext = model; + return new NewTabHost(view, view.TabablzControl); + } + + public TabEmptiedResponse TabEmptiedHandler(TabablzControl tabControl, Window window) + { + return TabEmptiedResponse.CloseWindow; + } + } +} \ No newline at end of file diff --git a/DragablzDemo/BoundExampleModel.cs b/DragablzDemo/BoundExampleModel.cs new file mode 100644 index 0000000..6981897 --- /dev/null +++ b/DragablzDemo/BoundExampleModel.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Dragablz; +using DragablzDemo.Annotations; + +namespace DragablzDemo +{ + public class BoundExampleModel + { + private readonly IInterTabClient _interTabClient = new BoundExampleInterTabClient(); + private readonly ObservableCollection _items; + + public BoundExampleModel() + { + _items = new ObservableCollection(); + } + + public BoundExampleModel(params SimpleViewModel[] items) + { + _items = new ObservableCollection(items); + } + + public ObservableCollection Items + { + get { return _items; } + } + + public static Guid TabPartition + { + get { return new Guid("2AE89D18-F236-4D20-9605-6C03319038E6"); } + } + + public IInterTabClient InterTabClient + { + get { return _interTabClient; } + } + } +} diff --git a/DragablzDemo/BoundExampleWindow.xaml b/DragablzDemo/BoundExampleWindow.xaml new file mode 100644 index 0000000..1eda35d --- /dev/null +++ b/DragablzDemo/BoundExampleWindow.xaml @@ -0,0 +1,41 @@ + + + + + + + + + None + + + + + + + + + + + + + + + + + + + diff --git a/DragablzDemo/BoundExampleWindow.xaml.cs b/DragablzDemo/BoundExampleWindow.xaml.cs new file mode 100644 index 0000000..09c113b --- /dev/null +++ b/DragablzDemo/BoundExampleWindow.xaml.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Shapes; + +namespace DragablzDemo +{ + /// + /// Interaction logic for BoundExampleWindow.xaml + /// + public partial class BoundExampleWindow : Window + { + public BoundExampleWindow() + { + InitializeComponent(); + } + } +} diff --git a/DragablzDemo/DragablzDemo.csproj b/DragablzDemo/DragablzDemo.csproj new file mode 100644 index 0000000..95cbade --- /dev/null +++ b/DragablzDemo/DragablzDemo.csproj @@ -0,0 +1,139 @@ + + + + Debug + x86 + 8.0.30703 + 2.0 + {63F4A11F-3B6A-43D3-B5A2-B9B9C74D6023} + WinExe + Properties + DragablzDemo + DragablzDemo + v4.5 + + + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + DragablzDemo.Boot + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + + + MSBuild:Compile + Designer + + + + App.xaml + Code + + + BasicExampleMainWindow.xaml + Code + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + + + BasicExampleTemplateWindow.xaml + + + + + + BoundExampleWindow.xaml + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + {7B11011C-7FD7-4AB0-A1AD-04E940B026DE} + Dragablz + + + + + \ No newline at end of file diff --git a/DragablzDemo/Properties/Annotations.cs b/DragablzDemo/Properties/Annotations.cs new file mode 100644 index 0000000..1e6b076 --- /dev/null +++ b/DragablzDemo/Properties/Annotations.cs @@ -0,0 +1,614 @@ +using System; + +#pragma warning disable 1591 +// ReSharper disable UnusedMember.Global +// ReSharper disable UnusedParameter.Local +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable UnusedAutoPropertyAccessor.Global +// ReSharper disable IntroduceOptionalParameters.Global +// ReSharper disable MemberCanBeProtected.Global +// ReSharper disable InconsistentNaming + +namespace DragablzDemo.Annotations +{ + /// + /// Indicates that the value of the marked element could be null sometimes, + /// so the check for null is necessary before its usage + /// + /// + /// [CanBeNull] public object Test() { return null; } + /// public void UseTest() { + /// var p = Test(); + /// var s = p.ToString(); // Warning: Possible 'System.NullReferenceException' + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | + AttributeTargets.Property | AttributeTargets.Delegate | + AttributeTargets.Field, AllowMultiple = false, Inherited = true)] + public sealed class CanBeNullAttribute : Attribute { } + + /// + /// Indicates that the value of the marked element could never be null + /// + /// + /// [NotNull] public object Foo() { + /// return null; // Warning: Possible 'null' assignment + /// } + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | + AttributeTargets.Property | AttributeTargets.Delegate | + AttributeTargets.Field, AllowMultiple = false, Inherited = true)] + public sealed class NotNullAttribute : Attribute { } + + /// + /// Indicates that the marked method builds string by format pattern and (optional) arguments. + /// Parameter, which contains format string, should be given in constructor. The format string + /// should be in -like form + /// + /// + /// [StringFormatMethod("message")] + /// public void ShowError(string message, params object[] args) { /* do something */ } + /// public void Foo() { + /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + /// } + /// + [AttributeUsage( + AttributeTargets.Constructor | AttributeTargets.Method, + AllowMultiple = false, Inherited = true)] + public sealed class StringFormatMethodAttribute : Attribute + { + /// + /// Specifies which parameter of an annotated method should be treated as format-string + /// + public StringFormatMethodAttribute(string formatParameterName) + { + FormatParameterName = formatParameterName; + } + + public string FormatParameterName { get; private set; } + } + + /// + /// Indicates that the function argument should be string literal and match one + /// of the parameters of the caller function. For example, ReSharper annotates + /// the parameter of + /// + /// + /// public void Foo(string param) { + /// if (param == null) + /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol + /// } + /// + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public sealed class InvokerParameterNameAttribute : Attribute { } + + /// + /// Indicates that the method is contained in a type that implements + /// interface + /// and this method is used to notify that some property value changed + /// + /// + /// The method should be non-static and conform to one of the supported signatures: + /// + /// NotifyChanged(string) + /// NotifyChanged(params string[]) + /// NotifyChanged{T}(Expression{Func{T}}) + /// NotifyChanged{T,U}(Expression{Func{T,U}}) + /// SetProperty{T}(ref T, T, string) + /// + /// + /// + /// public class Foo : INotifyPropertyChanged { + /// public event PropertyChangedEventHandler PropertyChanged; + /// [NotifyPropertyChangedInvocator] + /// protected virtual void NotifyChanged(string propertyName) { ... } + /// + /// private string _name; + /// public string Name { + /// get { return _name; } + /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } + /// } + /// } + /// + /// Examples of generated notifications: + /// + /// NotifyChanged("Property") + /// NotifyChanged(() => Property) + /// NotifyChanged((VM x) => x.Property) + /// SetProperty(ref myField, value, "Property") + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class NotifyPropertyChangedInvocatorAttribute : Attribute + { + public NotifyPropertyChangedInvocatorAttribute() { } + public NotifyPropertyChangedInvocatorAttribute(string parameterName) + { + ParameterName = parameterName; + } + + public string ParameterName { get; private set; } + } + + /// + /// Describes dependency between method input and output + /// + /// + ///

Function Definition Table syntax:

+ /// + /// FDT ::= FDTRow [;FDTRow]* + /// FDTRow ::= Input => Output | Output <= Input + /// Input ::= ParameterName: Value [, Input]* + /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} + /// Value ::= true | false | null | notnull | canbenull + /// + /// If method has single input parameter, it's name could be omitted.
+ /// Using halt (or void/nothing, which is the same) + /// for method output means that the methos doesn't return normally.
+ /// canbenull annotation is only applicable for output parameters.
+ /// You can use multiple [ContractAnnotation] for each FDT row, + /// or use single attribute with rows separated by semicolon.
+ ///
+ /// + /// + /// [ContractAnnotation("=> halt")] + /// public void TerminationMethod() + /// + /// + /// [ContractAnnotation("halt <= condition: false")] + /// public void Assert(bool condition, string text) // regular assertion method + /// + /// + /// [ContractAnnotation("s:null => true")] + /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() + /// + /// + /// // A method that returns null if the parameter is null, and not null if the parameter is not null + /// [ContractAnnotation("null => null; notnull => notnull")] + /// public object Transform(object data) + /// + /// + /// [ContractAnnotation("s:null=>false; =>true,result:notnull; =>false, result:null")] + /// public bool TryParse(string s, out Person result) + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public sealed class ContractAnnotationAttribute : Attribute + { + public ContractAnnotationAttribute([NotNull] string contract) + : this(contract, false) { } + + public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) + { + Contract = contract; + ForceFullStates = forceFullStates; + } + + public string Contract { get; private set; } + public bool ForceFullStates { get; private set; } + } + + /// + /// Indicates that marked element should be localized or not + /// + /// + /// [LocalizationRequiredAttribute(true)] + /// public class Foo { + /// private string str = "my string"; // Warning: Localizable string + /// } + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] + public sealed class LocalizationRequiredAttribute : Attribute + { + public LocalizationRequiredAttribute() : this(true) { } + public LocalizationRequiredAttribute(bool required) + { + Required = required; + } + + public bool Required { get; private set; } + } + + /// + /// Indicates that the value of the marked type (or its derivatives) + /// cannot be compared using '==' or '!=' operators and Equals() + /// should be used instead. However, using '==' or '!=' for comparison + /// with null is always permitted. + /// + /// + /// [CannotApplyEqualityOperator] + /// class NoEquality { } + /// class UsesNoEquality { + /// public void Test() { + /// var ca1 = new NoEquality(); + /// var ca2 = new NoEquality(); + /// if (ca1 != null) { // OK + /// bool condition = ca1 == ca2; // Warning + /// } + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Interface | AttributeTargets.Class | + AttributeTargets.Struct, AllowMultiple = false, Inherited = true)] + public sealed class CannotApplyEqualityOperatorAttribute : Attribute { } + + /// + /// When applied to a target attribute, specifies a requirement for any type marked + /// with the target attribute to implement or inherit specific type or types. + /// + /// + /// [BaseTypeRequired(typeof(IComponent)] // Specify requirement + /// public class ComponentAttribute : Attribute { } + /// [Component] // ComponentAttribute requires implementing IComponent interface + /// public class MyComponent : IComponent { } + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)] + [BaseTypeRequired(typeof(Attribute))] + public sealed class BaseTypeRequiredAttribute : Attribute + { + public BaseTypeRequiredAttribute([NotNull] Type baseType) + { + BaseType = baseType; + } + + [NotNull] public Type BaseType { get; private set; } + } + + /// + /// Indicates that the marked symbol is used implicitly + /// (e.g. via reflection, in external library), so this symbol + /// will not be marked as unused (as well as by other usage inspections) + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] + public sealed class UsedImplicitlyAttribute : Attribute + { + public UsedImplicitlyAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + + public UsedImplicitlyAttribute( + ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + public ImplicitUseKindFlags UseKindFlags { get; private set; } + public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + /// + /// Should be used on attributes and causes ReSharper + /// to not mark symbols marked with such attributes as unused + /// (as well as by other usage inspections) + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public sealed class MeansImplicitUseAttribute : Attribute + { + public MeansImplicitUseAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + + public MeansImplicitUseAttribute( + ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) + { + UseKindFlags = useKindFlags; + TargetFlags = targetFlags; + } + + [UsedImplicitly] public ImplicitUseKindFlags UseKindFlags { get; private set; } + [UsedImplicitly] public ImplicitUseTargetFlags TargetFlags { get; private set; } + } + + [Flags] + public enum ImplicitUseKindFlags + { + Default = Access | Assign | InstantiatedWithFixedConstructorSignature, + /// Only entity marked with attribute considered used + Access = 1, + /// Indicates implicit assignment to a member + Assign = 2, + /// + /// Indicates implicit instantiation of a type with fixed constructor signature. + /// That means any unused constructor parameters won't be reported as such. + /// + InstantiatedWithFixedConstructorSignature = 4, + /// Indicates implicit instantiation of a type + InstantiatedNoFixedConstructorSignature = 8, + } + + /// + /// Specify what is considered used implicitly + /// when marked with + /// or + /// + [Flags] + public enum ImplicitUseTargetFlags + { + Default = Itself, + Itself = 1, + /// Members of entity marked with attribute are considered used + Members = 2, + /// Entity marked with attribute and all its members considered used + WithMembers = Itself | Members + } + + /// + /// This attribute is intended to mark publicly available API + /// which should not be removed and so is treated as used + /// + [MeansImplicitUse] + public sealed class PublicAPIAttribute : Attribute + { + public PublicAPIAttribute() { } + public PublicAPIAttribute([NotNull] string comment) + { + Comment = comment; + } + + [NotNull] public string Comment { get; private set; } + } + + /// + /// Tells code analysis engine if the parameter is completely handled + /// when the invoked method is on stack. If the parameter is a delegate, + /// indicates that delegate is executed while the method is executed. + /// If the parameter is an enumerable, indicates that it is enumerated + /// while the method is executed + /// + [AttributeUsage(AttributeTargets.Parameter, Inherited = true)] + public sealed class InstantHandleAttribute : Attribute { } + + /// + /// Indicates that a method does not make any observable state changes. + /// The same as System.Diagnostics.Contracts.PureAttribute + /// + /// + /// [Pure] private int Multiply(int x, int y) { return x * y; } + /// public void Foo() { + /// const int a = 2, b = 2; + /// Multiply(a, b); // Waring: Return value of pure method is not used + /// } + /// + [AttributeUsage(AttributeTargets.Method, Inherited = true)] + public sealed class PureAttribute : Attribute { } + + /// + /// Indicates that a parameter is a path to a file or a folder + /// within a web project. Path can be relative or absolute, + /// starting from web root (~) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public class PathReferenceAttribute : Attribute + { + public PathReferenceAttribute() { } + public PathReferenceAttribute([PathReference] string basePath) + { + BasePath = basePath; + } + + [NotNull] public string BasePath { get; private set; } + } + + // ASP.NET MVC attributes + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute + { + public AspMvcAreaMasterLocationFormatAttribute(string format) { } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute + { + public AspMvcAreaPartialViewLocationFormatAttribute(string format) { } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcAreaViewLocationFormatAttribute : Attribute + { + public AspMvcAreaViewLocationFormatAttribute(string format) { } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcMasterLocationFormatAttribute : Attribute + { + public AspMvcMasterLocationFormatAttribute(string format) { } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcPartialViewLocationFormatAttribute : Attribute + { + public AspMvcPartialViewLocationFormatAttribute(string format) { } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class AspMvcViewLocationFormatAttribute : Attribute + { + public AspMvcViewLocationFormatAttribute(string format) { } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC action. If applied to a method, the MVC action name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String) + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcActionAttribute : Attribute + { + public AspMvcActionAttribute() { } + public AspMvcActionAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [NotNull] public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcAreaAttribute : PathReferenceAttribute + { + public AspMvcAreaAttribute() { } + public AspMvcAreaAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [NotNull] public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that + /// the parameter is an MVC controller. If applied to a method, + /// the MVC controller name is calculated implicitly from the context. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String) + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcControllerAttribute : Attribute + { + public AspMvcControllerAttribute() { } + public AspMvcControllerAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [NotNull] public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(String, String) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcMasterAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(String, Object) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcModelTypeAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that + /// the parameter is an MVC partial view. If applied to a method, + /// the MVC partial view name is calculated implicitly from the context. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String) + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcPartialViewAttribute : PathReferenceAttribute { } + + /// + /// ASP.NET MVC attribute. Allows disabling all inspections + /// for MVC views within a class or a method. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + public sealed class AspMvcSupressViewErrorAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcDisplayTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcEditorTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. + /// Use this attribute for custom wrappers similar to + /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String) + /// + [AttributeUsage(AttributeTargets.Parameter)] + public sealed class AspMvcTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view. If applied to a method, the MVC view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(Object) + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + public sealed class AspMvcViewAttribute : PathReferenceAttribute { } + + /// + /// ASP.NET MVC attribute. When applied to a parameter of an attribute, + /// indicates that this parameter is an MVC action name + /// + /// + /// [ActionName("Foo")] + /// public ActionResult Login(string returnUrl) { + /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK + /// return RedirectToAction("Bar"); // Error: Cannot resolve action + /// } + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] + public sealed class AspMvcActionSelectorAttribute : Attribute { } + + [AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Field, Inherited = true)] + public sealed class HtmlElementAttributesAttribute : Attribute + { + public HtmlElementAttributesAttribute() { } + public HtmlElementAttributesAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; private set; } + } + + [AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.Field | + AttributeTargets.Property, Inherited = true)] + public sealed class HtmlAttributeValueAttribute : Attribute + { + public HtmlAttributeValueAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; private set; } + } + + // Razor attributes + + /// + /// Razor attribute. Indicates that a parameter or a method is a Razor section. + /// Use this attribute for custom wrappers similar to + /// System.Web.WebPages.WebPageBase.RenderSection(String) + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, Inherited = true)] + public sealed class RazorSectionAttribute : Attribute { } +} \ No newline at end of file diff --git a/DragablzDemo/Properties/AssemblyInfo.cs b/DragablzDemo/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5b904fd --- /dev/null +++ b/DragablzDemo/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +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("SaucyDemo")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("SaucyDemo")] +[assembly: AssemblyCopyright("Copyright © 2013")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 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/DragablzDemo/Properties/Resources.Designer.cs b/DragablzDemo/Properties/Resources.Designer.cs new file mode 100644 index 0000000..13e7ad5 --- /dev/null +++ b/DragablzDemo/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34014 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DragablzDemo.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DragablzDemo.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/DragablzDemo/Properties/Resources.resx b/DragablzDemo/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/DragablzDemo/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DragablzDemo/Properties/Settings.Designer.cs b/DragablzDemo/Properties/Settings.Designer.cs new file mode 100644 index 0000000..02bacf2 --- /dev/null +++ b/DragablzDemo/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34014 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace DragablzDemo.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/DragablzDemo/Properties/Settings.settings b/DragablzDemo/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/DragablzDemo/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/DragablzDemo/SimpleViewModel.cs b/DragablzDemo/SimpleViewModel.cs new file mode 100644 index 0000000..c103535 --- /dev/null +++ b/DragablzDemo/SimpleViewModel.cs @@ -0,0 +1,35 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using DragablzDemo.Annotations; + +namespace DragablzDemo +{ + public class SimpleViewModel : INotifyPropertyChanged + { + private bool _isSelected; + + public string Name { get; set; } + + public object SimpleContent { get; set; } + + public bool IsSelected + { + get { return _isSelected; } + set + { + if (_isSelected == value) return; + _isSelected = value; + OnPropertyChanged(); + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); + } + } +} \ No newline at end of file diff --git a/DragablzDemo/app.config b/DragablzDemo/app.config new file mode 100644 index 0000000..51278a4 --- /dev/null +++ b/DragablzDemo/app.config @@ -0,0 +1,3 @@ + + +