I am having difficulty getting an attached property working on a WPF Tab Control. I have implemented the class defined in the CodeProject tutorial
http://www.codeproject.com/Articles/349140/WPF-TabControl-focus-behavior-with-invisible-tabs
defined below.
namespace MyNamespace
{
public static class TabControlBehavior
{
public static readonly DependencyProperty FocusFirstVisibleTabProperty =
DependencyProperty.RegisterAttached("FocusFirstVisibleTab",
typeof(bool),
typeof(TabControlBehavior),
new FrameworkPropertyMetadata(OnFocusFirstVisibleTabPropertyChanged));
/// <summary>Gets the focus first visible tab value of the given element.
/// </summary>
/// <param name="element">The element.</param>
/// <returns></returns>
public static bool GetFocusFirstVisibleTab(TabControl element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (bool)element.GetValue(FocusFirstVisibleTabProperty);
}
/// <summary>Sets the focus first visible tab value of the given element.
/// </summary>
/// <param name="element">The element.</param>
/// <param name="value">if set to <c>true</c> [value].</param>
public static void SetFocusFirstVisibleTab(TabControl element, bool value)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(FocusFirstVisibleTabProperty, value);
}
/// <summary>Determines whether the value of the dependency property <c>IsFocused</c> has change.
/// </summary>
/// <param name="d">The dependency object.</param>
/// <param name="e">The <see
/// cref="System.Windows.DependencyPropertyChangedEventArgs"/>
/// instance containing the event data.</param>
private static void OnFocusFirstVisibleTabPropertyChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var tabControl = d as TabControl;
if (tabControl != null)
{
// Attach or detach the event handlers.
if ((bool)e.NewValue)
{
// Enable the attached behavior.
tabControl.Items.CurrentChanged += new EventHandler(TabControl_Items_CurrentChanged);
var collection = tabControl.Items as INotifyCollectionChanged;
if (collection != null)
{
collection.CollectionChanged +=
new NotifyCollectionChangedEventHandler(TabControl_Items_CollectionChanged);
}
}
else
{
// Disable the attached behavior.
tabControl.Items.CurrentChanged -= new EventHandler(TabControl_Items_CurrentChanged);
var collection = tabControl.Items as INotifyCollectionChanged;
if (collection != null)
{
collection.CollectionChanged -=
new NotifyCollectionChangedEventHandler(TabControl_Items_CollectionChanged);
}
// Detach handlers from the tab items.
foreach (var item in tabControl.Items)
{
TabItem tab = item as TabItem;
if (tab != null)
{
tab.IsVisibleChanged -=
new DependencyPropertyChangedEventHandler(TabItem_IsVisibleChanged);
}
}
}
}
}
/// <summary>Handles the CollectionChanged event of the TabControl.Items collection.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see
/// cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/>
/// instance containing the event data.</param>
static void TabControl_Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Attach event handlers to each tab so that when the Visibility property changes of the selected tab,
// the focus can be shifted to the next (or previous, if not next tab available) tab.
var collection = sender as ItemCollection;
if (collection != null)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Remove:
case NotifyCollectionChangedAction.Replace:
// Attach event handlers to the Visibility and IsEnabled properties.
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
TabItem tab = item as TabItem;
if (tab != null)
{
tab.IsVisibleChanged +=
new DependencyPropertyChangedEventHandler(TabItem_IsVisibleChanged);
}
}
}
// Detach event handlers from old items.
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
TabItem tab = item as TabItem;
if (tab != null)
{
tab.IsVisibleChanged -=
new DependencyPropertyChangedEventHandler(TabItem_IsVisibleChanged);
}
}
}
break;
case NotifyCollectionChangedAction.Reset:
// Attach event handlers to the Visibility and IsEnabled properties.
foreach (var item in collection)
{
TabItem tab = item as TabItem;
if (tab != null)
{
tab.IsVisibleChanged +=
new DependencyPropertyChangedEventHandler(TabItem_IsVisibleChanged);
}
}
break;
case NotifyCollectionChangedAction.Move:
default:
break;
}
// Select the first element if necessary.
if (collection.Count > 0 && collection.CurrentItem == null)
{
collection.MoveCurrentToFirst();
}
}
}
/// <summary>Handles the CurrentChanged event of the TabControl.Items collection.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.EventArgs"/>
/// instance containing the event data.</param>
static void TabControl_Items_CurrentChanged(object sender, EventArgs e)
{
var collection = sender as ItemCollection;
if (collection != null)
{
UIElement element = collection.CurrentItem as UIElement;
if (element != null && element.Visibility != Visibility.Visible)
{
element.Dispatcher.BeginInvoke(new Action(() => collection.MoveCurrentToNext()),
System.Windows.Threading.DispatcherPriority.Input);
}
}
}
/// <summary>Handles the IsVisibleChanged event of the tab item.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see
/// cref="System.Windows.DependencyPropertyChangedEventArgs"/>
/// instance containing the event data.</param>
static void TabItem_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
TabItem tab = sender as TabItem;
if (tab != null && tab.IsSelected && tab.Visibility != Visibility.Visible)
{
// Move to the next tab item.
TabControl tabControl = tab.Parent as TabControl;
if (tabControl != null)
{
if (!tabControl.Items.MoveCurrentToNext())
{
// Could not move to next, try previous.
tabControl.Items.MoveCurrentToPrevious();
}
}
}
}
}
}
I then try to set the attached dependency property in my xaml code as follows :
<Window x:Class="MyNamespace.MyApp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d3="http://research.microsoft.com/DynamicDataDisplay/1.0"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:local="clr-namespace:MyNamespace">
<Window.Resources>
<ResourceDictionary>
<Style TargetType="{x:Type TabControl}">
<Setter Property="local:TabControlBehavior.FocusFirstVisibleTab" Value="True" />
</Style>
</ResourceDictionary>
</Window.Resources>
</Window>
However I can't compile due to the following error
MC4003: Cannot resolve the Style Property 'FocusFirstVisibleTab'. Verify that the owning type is the Style's TargetType, or use Class.Property syntax to specify the Property.
Thanks in advance for any help.
The problem was resolved by renaming MyNamespace to something other than the namespace containing my application.
Related
I have a FooUserControl which subscribes on it's LoadedEvent. This UserControl can be placed else where on your gui (on any Window or inside of any Control). To avoid leaks, I have implemented some kind of disposing.
The problem with this solution:
If you put the FooUserControl on a TabItem of a TabControl and change the tabs, the OnVisualParentChanged() is called and the subscription is disposed. If I wouldn't add this method, and you close the TabItem the subscription is still alive in background, although the UserControl can be disposed. The same problem will occur with a page
public class FooUserControl : UserControl
{
private IDisposable _Subscription;
public FooUserControl()
{
Loaded += _OnLoaded;
}
private void _OnLoaded(object sender, RoutedEventArgs e)
{
// avoid multiple subscribing
Loaded -= _OnLoaded;
// add hook to parent window to dispose subscription
var parentWindow = Window.GetWindow(this);
if(parentWindow != null)
parentWindow.Closed += _ParentWindowOnClosed;
_Subscription = MyObservableInstance.Subscribe(...);
}
private void _ParentWindowOnClosed(object? sender, EventArgs e)
{
_Dispose();
}
// check if the parent visual has been changed
// can happen if you use the control on a page
protected override void OnVisualParentChanged(DependencyObject oldParent)
{
if (oldParent != null)
{
_Dispose();
}
base.OnVisualParentChanged(oldParent);
}
private void _Dispose()
{
_Subscription?.Dispose();
}
}
I finally found a solution. In the UnLoaded event, I scan the Logical/VisualTree if there is still an instance present or not.
Since there is no real disposing mechanism in wpf, I have adopted this solution. I'm open for a better solution!
FooUserControl
public class FooUserControl : UserControl
{
private IDisposable _Subscription;
private Window _ParentWindow;
public FooUserControl()
{
Loaded += _OnLoaded;
Unloaded += _OnUnloaded;
}
private void _OnLoaded(object sender, RoutedEventArgs e)
{
// avoid multiple subscribing
Loaded -= _OnLoaded;
// add hook to parent window to dispose subscription
_ParentWindow = Window.GetWindow(this);
_ParentWindow.Closed += _ParentWindowOnClosed;
_Subscription = MyObservableInstance.Subscribe(...);
}
private void _OnUnloaded(object sender, RoutedEventArgs e)
{
// look in logical and visual tree if the control has been removed
if (_ParentWindow.FindChildByUid<NLogViewer>(Uid) == null)
{
_Dispose();
}
}
private void _ParentWindowOnClosed(object? sender, EventArgs e)
{
_Dispose();
}
private void _Dispose()
{
_Subscription?.Dispose();
}
}
DependencyObjectExtensions
public static class DependencyObjectExtensions
{
/// <summary>
/// Analyzes both visual and logical tree in order to find all elements of a given
/// type that are descendants of the <paramref name="source"/> item.
/// </summary>
/// <typeparam name="T">The type of the queried items.</typeparam>
/// <param name="source">The root element that marks the source of the search. If the
/// source is already of the requested type, it will not be included in the result.</param>
/// <param name="uid">The UID of the <see cref="UIElement"/></param>
/// <returns>All descendants of <paramref name="source"/> that match the requested type.</returns>
public static T FindChildByUid<T>(this DependencyObject source, string uid) where T : UIElement
{
if (source != null)
{
var childs = GetChildObjects(source);
foreach (DependencyObject child in childs)
{
//analyze if children match the requested type
if (child != null && child is T dependencyObject && dependencyObject.Uid.Equals(uid))
{
return dependencyObject;
}
var descendant = FindChildByUid<T>(child, uid);
if (descendant != null)
return descendant;
}
}
return null;
}
/// <summary>
/// This method is an alternative to WPF's
/// <see cref="VisualTreeHelper.GetChild"/> method, which also
/// supports content elements. Keep in mind that for content elements,
/// this method falls back to the logical tree of the element.
/// </summary>
/// <param name="parent">The item to be processed.</param>
/// <returns>The submitted item's child elements, if available.</returns>
public static IEnumerable<DependencyObject> GetChildObjects(this DependencyObject parent)
{
if (parent == null) yield break;
if (parent is ContentElement || parent is FrameworkElement)
{
//use the logical tree for content / framework elements
foreach (object obj in LogicalTreeHelper.GetChildren(parent))
{
var depObj = obj as DependencyObject;
if (depObj != null) yield return (DependencyObject) obj;
}
}
else
{
//use the visual tree per default
int count = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < count; i++)
{
yield return VisualTreeHelper.GetChild(parent, i);
}
}
}
}
I have a class that extends Combobox. In this class I can ONLY disable the togglebutton. I still want users to click into the combobox and type something in to filter its contents.
If you know any other way to destroy the togglebutton please let me know (as long as it doesn't involve recreating the template - I've tried and failed and the generated combobox template code is to large to dump in stackoverflow so I can't solve it that way. )
In the beginning I wanted to be able to toggle the togglebutton (if there less than 10 item left in the filter allow a user to click the togglebutton to show the list of items if there are more the togglebutton would disappear) . At this point I'll take any solution that shrinks, hides, moves, intentionally breaks, removes, buries alive, replaces either the togglebutton or the mouse on click event that tells the combobox that it should show its popup or anything in between that will stop this action.
This is my third question trying to find something that will work. Honestly any suggestions would be helpful (just no copy and edit the template please go to my other question where I ask help on how to implement that solution)
using Analytics_Module.Models;
using System;
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
namespace Analytics_Module.UI_Components
{
class MultiselectFilteredComboBox : ComboBox
{
////
// Public Fields
////
/// <summary>
/// The search string treshold length.
/// </summary>
/// <remarks>
/// It's implemented as a Dependency Property, so you can set it in a XAML template
/// </remarks>
public static readonly DependencyProperty MinimumSearchLengthProperty =
DependencyProperty.Register(
"MinimumSearchLength",
typeof(int),
typeof(MultiselectFilteredComboBox),
new UIPropertyMetadata(3));
////
// Private Fields
////
/// <summary>
/// Caches the previous value of the filter.
/// </summary>
private string oldFilter = string.Empty;
/// <summary>
/// Holds the current value of the filter.
/// </summary>
private string currentFilter = string.Empty;
public static T FindChild<T>(DependencyObject parent, string childName)
where T : DependencyObject
{
// Confirm parent and childName are valid.
if (parent == null) return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
// If the child is not of the request child type child
T childType = child as T;
if (childType == null)
{
// recursively drill down the tree
foundChild = FindChild<T>(child, childName);
// If the child is found, break so we do not overwrite the found child.
if (foundChild != null) break;
}
else if (!string.IsNullOrEmpty(childName))
{
var frameworkElement = child as FrameworkElement;
// If the child's name is set for search
if (frameworkElement != null && frameworkElement.Name == childName)
{
// if the child's name is of the request name
foundChild = (T)child;
break;
}
}
else
{
// child element found.
foundChild = (T)child;
break;
}
}
return foundChild;
}
////
// Constructors
////
/// <summary>
/// Initializes a new instance of the FilteredComboBox class.
/// </summary>
/// <remarks>
/// You could set 'IsTextSearchEnabled' to 'false' here,
/// to avoid non-intuitive behavior of the control
/// </remarks>
public MultiselectFilteredComboBox()
{
}
////
// Properties
////
/// <summary>
/// Gets or sets the search string treshold length.
/// </summary>
/// <value>The minimum length of the search string that triggers filtering.</value>
[Description("Length of the search string that triggers filtering.")]
[Category("Filtered ComboBox")]
[DefaultValue(3)]
public int MinimumSearchLength
{
[System.Diagnostics.DebuggerStepThrough]
get
{
return (int)this.GetValue(MinimumSearchLengthProperty);
}
[System.Diagnostics.DebuggerStepThrough]
set
{
this.SetValue(MinimumSearchLengthProperty, value);
}
}
/// <summary>
/// Gets a reference to the internal editable textbox.
/// </summary>
/// <value>A reference to the internal editable textbox.</value>
/// <remarks>
/// We need this to get access to the Selection.
/// </remarks>
protected TextBox EditableTextBox
{
get
{
return this.GetTemplateChild("PART_EditableTextBox") as TextBox;
}
}
////
// Event Raiser Overrides
////
/// <summary>
/// Keep the filter if the ItemsSource is explicitly changed.
/// </summary>
/// <param name="oldValue">The previous value of the filter.</param>
/// <param name="newValue">The current value of the filter.</param>
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += this.FilterPredicate;
}
if (oldValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(oldValue);
view.Filter -= this.FilterPredicate;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
/// <summary>
/// Confirm or cancel the selection when Tab, Enter, or Escape are hit.
/// Open the DropDown when the Down Arrow is hit.
/// </summary>
/// <param name="e">Key Event Args.</param>
/// <remarks>
/// The 'KeyDown' event is not raised for Arrows, Tab and Enter keys.
/// It is swallowed by the DropDown if it's open.
/// So use the Preview instead.
/// </remarks>
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Tab || e.Key == Key.Enter)
{
// Explicit Selection -> Close ItemsPanel
this.IsDropDownOpen = false;
}
else if (e.Key == Key.Escape)
{
// Escape -> Close DropDown and redisplay Filter
this.IsDropDownOpen = false;
this.SelectedIndex = -1;
this.Text = this.currentFilter;
}
else
{
if (e.Key == Key.Down)
{
// Arrow Down -> Open DropDown
this.IsDropDownOpen = true;
}
base.OnPreviewKeyDown(e);
}
// Cache text
this.oldFilter = this.Text;
}
/// <summary>
/// Modify and apply the filter.
/// </summary>
/// <param name="e">Key Event Args.</param>
/// <remarks>
/// Alternatively, you could react on 'OnTextChanged', but navigating through
/// the DropDown will also change the text.
/// </remarks>
protected override void OnKeyUp(KeyEventArgs e)
{
if (e.Key == Key.Up || e.Key == Key.Down)
{
// Navigation keys are ignored
}
else if (e.Key == Key.Tab || e.Key == Key.Enter)
{
// Explicit Select -> Clear Filter
this.ClearFilter();
}
else
{
// The text was changed
if (this.Text != this.oldFilter)
{
// Clear the filter if the text is empty,
// apply the filter if the text is long enough
if (this.Text.Length == 0 || this.Text.Length >= this.MinimumSearchLength)
{
this.RefreshFilter();
this.IsDropDownOpen = true;
// Unselect
this.EditableTextBox.SelectionStart = int.MaxValue;
}
}
base.OnKeyUp(e);
// Update Filter Value
this.currentFilter = this.Text;
}
}
/// <summary>
/// Make sure the text corresponds to the selection when leaving the control.
/// </summary>
/// <param name="e">A KeyBoardFocusChangedEventArgs.</param>
protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
{
//this.ClearFilter();
//int temp = this.SelectedIndex;
//this.SelectedIndex = -1;
//this.Text = string.Empty;
//this.SelectedIndex = temp;
//base.OnPreviewLostKeyboardFocus(e);
}
////
// Helpers
////
/// <summary>
/// Re-apply the Filter.
/// </summary>
private void RefreshFilter()
{
if (this.ItemsSource != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(this.ItemsSource);
view.Refresh();
}
}
/// <summary>
/// Clear the Filter.
/// </summary>
private void ClearFilter()
{
this.currentFilter = string.Empty;
this.RefreshFilter();
}
/// <summary>
/// The Filter predicate that will be applied to each row in the ItemsSource.
/// </summary>
/// <param name="value">A row in the ItemsSource.</param>
/// <returns>Whether or not the item will appear in the DropDown.</returns>
private bool FilterPredicate(object value)
{
MultiSelectDropDownListEntry tmp = (MultiSelectDropDownListEntry)value;
// No filter, no text
if (value == null)
{
return false;
}
// No text, no filter
if (this.Text.Length == 0)
{
return true;
}
// Case insensitive search
return tmp.Name.ToString().ToLower().Contains(this.Text.ToLower());
}
}
}
Found a way.
I added this to the class
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
(((this.GetVisualChild(0) as Grid).Children)[1] as System.Windows.Controls.Primitives.ToggleButton).IsEnabled = false;
}
I have seen some questions on SO but none of them seemed applicable for me. I want to be able to use the great Avalondock 2.0 with Prism 4. However, all the sample region adapters for that is for Avalondock 1.x series, that I cannot get it working.
Does anyone have sample code on how to create a Region Adapter for AvalonDock's LayoutDocumentPane and LayoutAnchorablePane?
Unfortunately, to the best of my knowledge, both the "LayoutDocumentPane" and the "LayoutAnchorablePane" do not allow the inclusion/creation of RegionAdapters, however the "DockingManager" does. One solution would be to create a RegionAdapter for the DockingManager which would then manage the instantiation of "LayoutDocuments" within the visual tree.
The xaml would look as follows:
<ad:DockingManager Background="AliceBlue" x:Name="WorkspaceRegion" prism:RegionManager.RegionName="WorkspaceRegion">
<ad:LayoutRoot>
<ad:LayoutPanel>
<ad:LayoutDocumentPaneGroup>
<ad:LayoutDocumentPane>
</ad:LayoutDocumentPane>
</ad:LayoutDocumentPaneGroup>
</ad:LayoutPanel>
</ad:LayoutRoot>
</ad:DockingManager>
Note that the region is defined in the DockingManager tag and there exists a single LayoutDocumentPaneGroup under LayoutPanel. The LayoutDocumentPane under the LayoutDocumentPaneGroup will host the LayoutDocuments associated to the views to be added to the "WorkspaceRegion".
As for the RegionAdapter itself refer to the code below which I provided with explanatory comments
#region Constructor
public AvalonDockRegionAdapter(IRegionBehaviorFactory factory)
: base(factory)
{
}
#endregion //Constructor
#region Overrides
protected override IRegion CreateRegion()
{
return new AllActiveRegion();
}
protected override void Adapt(IRegion region, DockingManager regionTarget)
{
region.Views.CollectionChanged += delegate(
Object sender, NotifyCollectionChangedEventArgs e)
{
this.OnViewsCollectionChanged(sender, e, region, regionTarget);
};
regionTarget.DocumentClosed += delegate(
Object sender, DocumentClosedEventArgs e)
{
this.OnDocumentClosedEventArgs(sender, e, region);
};
}
#endregion //Overrides
#region Event Handlers
/// <summary>
/// Handles the NotifyCollectionChangedEventArgs event.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The event.</param>
/// <param name="region">The region.</param>
/// <param name="regionTarget">The region target.</param>
void OnViewsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e, IRegion region, DockingManager regionTarget)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (FrameworkElement item in e.NewItems)
{
UIElement view = item as UIElement;
if (view != null)
{
//Create a new layout document to be included in the LayoutDocuemntPane (defined in xaml)
LayoutDocument newLayoutDocument = new LayoutDocument();
//Set the content of the LayoutDocument
newLayoutDocument.Content = item;
ViewModelBase_2 viewModel = (ViewModelBase_2)item.DataContext;
if (viewModel != null)
{
//All my viewmodels have properties DisplayName and IconKey
newLayoutDocument.Title = viewModel.DisplayName;
//GetImageUri is custom made method which gets the icon for the LayoutDocument
newLayoutDocument.IconSource = this.GetImageUri(viewModel.IconKey);
}
//Store all LayoutDocuments already pertaining to the LayoutDocumentPane (defined in xaml)
List<LayoutDocument> oldLayoutDocuments = new List<LayoutDocument>();
//Get the current ILayoutDocumentPane ... Depending on the arrangement of the views this can be either
//a simple LayoutDocumentPane or a LayoutDocumentPaneGroup
ILayoutDocumentPane currentILayoutDocumentPane = (ILayoutDocumentPane)regionTarget.Layout.RootPanel.Children[0];
if (currentILayoutDocumentPane.GetType() == typeof(LayoutDocumentPaneGroup))
{
//If the current ILayoutDocumentPane turns out to be a group
//Get the children (LayoutDocuments) of the first pane
LayoutDocumentPane oldLayoutDocumentPane = (LayoutDocumentPane)currentILayoutDocumentPane.Children.ToList()[0];
foreach (LayoutDocument child in oldLayoutDocumentPane.Children)
{
oldLayoutDocuments.Insert(0, child);
}
}
else if (currentILayoutDocumentPane.GetType() == typeof(LayoutDocumentPane))
{
//If the current ILayoutDocumentPane turns out to be a simple pane
//Get the children (LayoutDocuments) of the single existing pane.
foreach (LayoutDocument child in currentILayoutDocumentPane.Children)
{
oldLayoutDocuments.Insert(0, child);
}
}
//Create a new LayoutDocumentPane and inserts your new LayoutDocument
LayoutDocumentPane newLayoutDocumentPane = new LayoutDocumentPane();
newLayoutDocumentPane.InsertChildAt(0, newLayoutDocument);
//Append to the new LayoutDocumentPane the old LayoutDocuments
foreach (LayoutDocument doc in oldLayoutDocuments)
{
newLayoutDocumentPane.InsertChildAt(0, doc);
}
//Traverse the visual tree of the xaml and replace the LayoutDocumentPane (or LayoutDocumentPaneGroup) in xaml
//with your new LayoutDocumentPane (or LayoutDocumentPaneGroup)
if (currentILayoutDocumentPane.GetType() == typeof(LayoutDocumentPane))
regionTarget.Layout.RootPanel.ReplaceChildAt(0, newLayoutDocumentPane);
else if (currentILayoutDocumentPane.GetType() == typeof(LayoutDocumentPaneGroup))
{
currentILayoutDocumentPane.ReplaceChild(currentILayoutDocumentPane.Children.ToList()[0], newLayoutDocumentPane);
regionTarget.Layout.RootPanel.ReplaceChildAt(0, currentILayoutDocumentPane);
}
newLayoutDocument.IsActive = true;
}
}
}
}
/// <summary>
/// Handles the DocumentClosedEventArgs event raised by the DockingNanager when
/// one of the LayoutContent it hosts is closed.
/// </summary>
/// <param name="sender">The sender</param>
/// <param name="e">The event.</param>
/// <param name="region">The region.</param>
void OnDocumentClosedEventArgs(object sender, DocumentClosedEventArgs e, IRegion region)
{
region.Remove(e.Document.Content);
}
#endregion //Event handlers
Do not forget to add the code below in your Bootstrapper so that Prism is aware of the existence of your RegionAdapter
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
// Call base method
var mappings = base.ConfigureRegionAdapterMappings();
if (mappings == null) return null;
// Add custom mappings
mappings.RegisterMapping(typeof(DockingManager),
ServiceLocator.Current.GetInstance<AvalonDockRegionAdapter>());
// Set return value
return mappings;
}
VoilĂ . I know this is not the cleanest of solutions but it should work. The same approach can easily be applied to "LayoutAnchorablePane".
Live long and prosper!
I am trying to make an attached behavior to reorder rows bby doing Drag & Drop
I found some solution (On Stackoverflow and by googling) and using them i am trying to make the behavior... I took the example from Hordcodenet website (i dont have the link now)
Code
public static class DragDropRowBehavior
{
private static DataGrid dataGrid;
private static Popup popup;
private static bool enable;
private static object draggedItem;
public static object DraggedItem
{
get { return DragDropRowBehavior.draggedItem; }
set { DragDropRowBehavior.draggedItem = value; }
}
public static Popup GetPopupControl(DependencyObject obj)
{
return (Popup)obj.GetValue(PopupControlProperty);
}
public static void SetPopupControl(DependencyObject obj, Popup value)
{
obj.SetValue(PopupControlProperty, value);
}
// Using a DependencyProperty as the backing store for PopupControl. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PopupControlProperty =
DependencyProperty.RegisterAttached("PopupControl", typeof(Popup), typeof(DragDropRowBehavior), new UIPropertyMetadata(null, OnPopupControlChanged));
private static void OnPopupControlChanged(DependencyObject depObject, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null || !(e.NewValue is Popup))
{
throw new ArgumentException("Popup Control should be set", "PopupControl");
}
popup = e.NewValue as Popup;
dataGrid = depObject as DataGrid;
// Check if DataGrid
if (dataGrid == null)
return;
if (enable && popup != null)
{
dataGrid.BeginningEdit += new EventHandler<DataGridBeginningEditEventArgs>(OnBeginEdit);
dataGrid.CellEditEnding += new EventHandler<DataGridCellEditEndingEventArgs>(OnEndEdit);
dataGrid.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(OnMouseLeftButtonUp);
dataGrid.MouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown);
dataGrid.MouseMove += new MouseEventHandler(OnMouseMove);
}
else
{
dataGrid.BeginningEdit -= new EventHandler<DataGridBeginningEditEventArgs>(OnBeginEdit);
dataGrid.CellEditEnding -= new EventHandler<DataGridCellEditEndingEventArgs>(OnEndEdit);
dataGrid.MouseLeftButtonUp -= new System.Windows.Input.MouseButtonEventHandler(OnMouseLeftButtonUp);
dataGrid.MouseLeftButtonDown -= new MouseButtonEventHandler(OnMouseLeftButtonDown);
dataGrid.MouseMove -= new MouseEventHandler(OnMouseMove);
dataGrid = null;
popup = null;
draggedItem = null;
IsEditing = false;
IsDragging = false;
}
}
public static bool GetEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(EnabledProperty);
}
public static void SetEnabled(DependencyObject obj, bool value)
{
obj.SetValue(EnabledProperty, value);
}
// Using a DependencyProperty as the backing store for Enabled. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EnabledProperty =
DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(DragDropRowBehavior), new UIPropertyMetadata(false,OnEnabledChanged));
private static void OnEnabledChanged(DependencyObject depObject,DependencyPropertyChangedEventArgs e)
{
//Check if value is a Boolean Type
if (e.NewValue is bool == false)
throw new ArgumentException("Value should be of bool type", "Enabled");
enable = (bool)e.NewValue;
}
public static bool IsEditing { get; set; }
public static bool IsDragging { get; set; }
private static void OnBeginEdit(object sender, DataGridBeginningEditEventArgs e)
{
IsEditing = true;
//in case we are in the middle of a drag/drop operation, cancel it...
if (IsDragging) ResetDragDrop();
}
private static void OnEndEdit(object sender, DataGridCellEditEndingEventArgs e)
{
IsEditing = false;
}
/// <summary>
/// Initiates a drag action if the grid is not in edit mode.
/// </summary>
private static void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (IsEditing) return;
var row = UIHelpers.TryFindFromPoint<DataGridRow>((UIElement)sender, e.GetPosition(dataGrid));
if (row == null || row.IsEditing) return;
//set flag that indicates we're capturing mouse movements
IsDragging = true;
DraggedItem = row.Item;
}
/// <summary>
/// Completes a drag/drop operation.
/// </summary>
private static void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!IsDragging || IsEditing)
{
return;
}
//get the target item
var targetItem = dataGrid.SelectedItem;
if (targetItem == null || !ReferenceEquals(DraggedItem, targetItem))
{
//remove the source from the list
((dataGrid).ItemsSource as IList).Remove(DraggedItem);
//get target index
var targetIndex = ((dataGrid).ItemsSource as IList).IndexOf(targetItem);
//move source at the target's location
((dataGrid).ItemsSource as IList).Insert(targetIndex, DraggedItem);
//select the dropped item
dataGrid.SelectedItem = DraggedItem;
}
//reset
ResetDragDrop();
}
/// <summary>
/// Closes the popup and resets the
/// grid to read-enabled mode.
/// </summary>
private static void ResetDragDrop()
{
IsDragging = false;
popup.IsOpen = false;
dataGrid.IsReadOnly = false;
}
/// <summary>
/// Updates the popup's position in case of a drag/drop operation.
/// </summary>
private static void OnMouseMove(object sender, MouseEventArgs e)
{
if (!IsDragging || e.LeftButton != MouseButtonState.Pressed) return;
//display the popup if it hasn't been opened yet
if (!popup.IsOpen)
{
//switch to read-only mode
dataGrid.IsReadOnly = true;
//make sure the popup is visible
popup.IsOpen = true;
}
Size popupSize = new Size(popup.ActualWidth, popup.ActualHeight);
popup.PlacementRectangle = new Rect(e.GetPosition(dataGrid), popupSize);
//make sure the row under the grid is being selected
Point position = e.GetPosition(dataGrid);
var row = UIHelpers.TryFindFromPoint<DataGridRow>(dataGrid, position);
if (row != null) dataGrid.SelectedItem = row.Item;
}
}
Helper Class
///
/// Common UI related helper methods.
///
public static class UIHelpers
{
#region find parent
/// <summary>
/// Finds a parent of a given item on the visual tree.
/// </summary>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="child">A direct or indirect child of the
/// queried item.</param>
/// <returns>The first parent item that matches the submitted
/// type parameter. If not matching item can be found, a null
/// reference is being returned.</returns>
public static T TryFindParent<T>(DependencyObject child)
where T : DependencyObject
{
//get parent item
DependencyObject parentObject = GetParentObject(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
{
return parent;
}
else
{
//use recursion to proceed with next level
return TryFindParent<T>(parentObject);
}
}
/// <summary>
/// This method is an alternative to WPF's
/// <see cref="VisualTreeHelper.GetParent"/> method, which also
/// supports content elements. Do note, that for content element,
/// this method falls back to the logical tree of the element.
/// </summary>
/// <param name="child">The item to be processed.</param>
/// <returns>The submitted item's parent, if available. Otherwise
/// null.</returns>
public static DependencyObject GetParentObject(DependencyObject child)
{
if (child == null) return null;
ContentElement contentElement = child as ContentElement;
if (contentElement != null)
{
DependencyObject parent = ContentOperations.GetParent(contentElement);
if (parent != null) return parent;
FrameworkContentElement fce = contentElement as FrameworkContentElement;
return fce != null ? fce.Parent : null;
}
//if it's not a ContentElement, rely on VisualTreeHelper
return VisualTreeHelper.GetParent(child);
}
#endregion
#region update binding sources
/// <summary>
/// Recursively processes a given dependency object and all its
/// children, and updates sources of all objects that use a
/// binding expression on a given property.
/// </summary>
/// <param name="obj">The dependency object that marks a starting
/// point. This could be a dialog window or a panel control that
/// hosts bound controls.</param>
/// <param name="properties">The properties to be updated if
/// <paramref name="obj"/> or one of its childs provide it along
/// with a binding expression.</param>
public static void UpdateBindingSources(DependencyObject obj,
params DependencyProperty[] properties)
{
foreach (DependencyProperty depProperty in properties)
{
//check whether the submitted object provides a bound property
//that matches the property parameters
BindingExpression be = BindingOperations.GetBindingExpression(obj, depProperty);
if (be != null) be.UpdateSource();
}
int count = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < count; i++)
{
//process child items recursively
DependencyObject childObject = VisualTreeHelper.GetChild(obj, i);
UpdateBindingSources(childObject, properties);
}
}
#endregion
/// <summary>
/// Tries to locate a given item within the visual tree,
/// starting with the dependency object at a given position.
/// </summary>
/// <typeparam name="T">The type of the element to be found
/// on the visual tree of the element at the given location.</typeparam>
/// <param name="reference">The main element which is used to perform
/// hit testing.</param>
/// <param name="point">The position to be evaluated on the origin.</param>
public static T TryFindFromPoint<T>(UIElement reference, Point point)
where T : DependencyObject
{
DependencyObject element = reference.InputHitTest(point)
as DependencyObject;
if (element == null) return null;
else if (element is T) return (T)element;
else return TryFindParent<T>(element);
}
}
Problem is that the Event OnMouseLeftButtonDown is not called when i press it over a row to drag it... but OnMouseLeftButtonUp is called after that....
Is there ne way to do this....
I cant seem to find a way
Finally i got the Problem and also made some changes for this to work properly
I used this example to make the Drag Drop Logic amd made this behavior may be its use ful to others .... please suggest improvements i would be happy to change ...
Behavior
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using Microsoft.Windows.Controls;
using System.Windows.Input;
using System.Collections;
namespace DataGridDragAndDrop
{
public static class DragDropRowBehavior
{
private static DataGrid dataGrid;
private static Popup popup;
private static bool enable;
private static object draggedItem;
public static object DraggedItem
{
get { return DragDropRowBehavior.draggedItem; }
set { DragDropRowBehavior.draggedItem = value; }
}
public static Popup GetPopupControl(DependencyObject obj)
{
return (Popup)obj.GetValue(PopupControlProperty);
}
public static void SetPopupControl(DependencyObject obj, Popup value)
{
obj.SetValue(PopupControlProperty, value);
}
// Using a DependencyProperty as the backing store for PopupControl. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PopupControlProperty =
DependencyProperty.RegisterAttached("PopupControl", typeof(Popup), typeof(DragDropRowBehavior), new UIPropertyMetadata(null, OnPopupControlChanged));
private static void OnPopupControlChanged(DependencyObject depObject, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null || !(e.NewValue is Popup))
{
throw new ArgumentException("Popup Control should be set", "PopupControl");
}
popup = e.NewValue as Popup;
dataGrid = depObject as DataGrid;
// Check if DataGrid
if (dataGrid == null)
return;
if (enable && popup != null)
{
dataGrid.BeginningEdit += new EventHandler<DataGridBeginningEditEventArgs>(OnBeginEdit);
dataGrid.CellEditEnding += new EventHandler<DataGridCellEditEndingEventArgs>(OnEndEdit);
dataGrid.MouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(OnMouseLeftButtonUp);
dataGrid.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown);
dataGrid.MouseMove += new MouseEventHandler(OnMouseMove);
}
else
{
dataGrid.BeginningEdit -= new EventHandler<DataGridBeginningEditEventArgs>(OnBeginEdit);
dataGrid.CellEditEnding -= new EventHandler<DataGridCellEditEndingEventArgs>(OnEndEdit);
dataGrid.MouseLeftButtonUp -= new System.Windows.Input.MouseButtonEventHandler(OnMouseLeftButtonUp);
dataGrid.MouseLeftButtonDown -= new MouseButtonEventHandler(OnMouseLeftButtonDown);
dataGrid.MouseMove -= new MouseEventHandler(OnMouseMove);
dataGrid = null;
popup = null;
draggedItem = null;
IsEditing = false;
IsDragging = false;
}
}
public static bool GetEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(EnabledProperty);
}
public static void SetEnabled(DependencyObject obj, bool value)
{
obj.SetValue(EnabledProperty, value);
}
// Using a DependencyProperty as the backing store for Enabled. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EnabledProperty =
DependencyProperty.RegisterAttached("Enabled", typeof(bool), typeof(DragDropRowBehavior), new UIPropertyMetadata(false,OnEnabledChanged));
private static void OnEnabledChanged(DependencyObject depObject,DependencyPropertyChangedEventArgs e)
{
//Check if value is a Boolean Type
if (e.NewValue is bool == false)
throw new ArgumentException("Value should be of bool type", "Enabled");
enable = (bool)e.NewValue;
}
public static bool IsEditing { get; set; }
public static bool IsDragging { get; set; }
private static void OnBeginEdit(object sender, DataGridBeginningEditEventArgs e)
{
IsEditing = true;
//in case we are in the middle of a drag/drop operation, cancel it...
if (IsDragging) ResetDragDrop();
}
private static void OnEndEdit(object sender, DataGridCellEditEndingEventArgs e)
{
IsEditing = false;
}
/// <summary>
/// Initiates a drag action if the grid is not in edit mode.
/// </summary>
private static void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (IsEditing) return;
var row = UIHelpers.TryFindFromPoint<DataGridRow>((UIElement)sender, e.GetPosition(dataGrid));
if (row == null || row.IsEditing) return;
//set flag that indicates we're capturing mouse movements
IsDragging = true;
DraggedItem = row.Item;
}
/// <summary>
/// Completes a drag/drop operation.
/// </summary>
private static void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!IsDragging || IsEditing)
{
return;
}
//get the target item
var targetItem = dataGrid.SelectedItem;
if (targetItem == null || !ReferenceEquals(DraggedItem, targetItem))
{
//get target index
var targetIndex = ((dataGrid).ItemsSource as IList).IndexOf(targetItem);
//remove the source from the list
((dataGrid).ItemsSource as IList).Remove(DraggedItem);
//move source at the target's location
((dataGrid).ItemsSource as IList).Insert(targetIndex, DraggedItem);
//select the dropped item
dataGrid.SelectedItem = DraggedItem;
}
//reset
ResetDragDrop();
}
/// <summary>
/// Closes the popup and resets the
/// grid to read-enabled mode.
/// </summary>
private static void ResetDragDrop()
{
IsDragging = false;
popup.IsOpen = false;
dataGrid.IsReadOnly = false;
}
/// <summary>
/// Updates the popup's position in case of a drag/drop operation.
/// </summary>
private static void OnMouseMove(object sender, MouseEventArgs e)
{
if (!IsDragging || e.LeftButton != MouseButtonState.Pressed) return;
popup.DataContext = DraggedItem;
//display the popup if it hasn't been opened yet
if (!popup.IsOpen)
{
//switch to read-only mode
dataGrid.IsReadOnly = true;
//make sure the popup is visible
popup.IsOpen = true;
}
Size popupSize = new Size(popup.ActualWidth, popup.ActualHeight);
popup.PlacementRectangle = new Rect(e.GetPosition(dataGrid), popupSize);
//make sure the row under the grid is being selected
Point position = e.GetPosition(dataGrid);
var row = UIHelpers.TryFindFromPoint<DataGridRow>(dataGrid, position);
if (row != null) dataGrid.SelectedItem = row.Item;
}
}
}
UIHelper Class
public static class UIHelpers
{
#region find parent
/// <summary>
/// Finds a parent of a given item on the visual tree.
/// </summary>
/// <typeparam name="T">The type of the queried item.</typeparam>
/// <param name="child">A direct or indirect child of the
/// queried item.</param>
/// <returns>The first parent item that matches the submitted
/// type parameter. If not matching item can be found, a null
/// reference is being returned.</returns>
public static T TryFindParent<T>(DependencyObject child)
where T : DependencyObject
{
//get parent item
DependencyObject parentObject = GetParentObject(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
{
return parent;
}
else
{
//use recursion to proceed with next level
return TryFindParent<T>(parentObject);
}
}
/// <summary>
/// This method is an alternative to WPF's
/// <see cref="VisualTreeHelper.GetParent"/> method, which also
/// supports content elements. Do note, that for content element,
/// this method falls back to the logical tree of the element.
/// </summary>
/// <param name="child">The item to be processed.</param>
/// <returns>The submitted item's parent, if available. Otherwise
/// null.</returns>
public static DependencyObject GetParentObject(DependencyObject child)
{
if (child == null) return null;
ContentElement contentElement = child as ContentElement;
if (contentElement != null)
{
DependencyObject parent = ContentOperations.GetParent(contentElement);
if (parent != null) return parent;
FrameworkContentElement fce = contentElement as FrameworkContentElement;
return fce != null ? fce.Parent : null;
}
//if it's not a ContentElement, rely on VisualTreeHelper
return VisualTreeHelper.GetParent(child);
}
#endregion
#region update binding sources
/// <summary>
/// Recursively processes a given dependency object and all its
/// children, and updates sources of all objects that use a
/// binding expression on a given property.
/// </summary>
/// <param name="obj">The dependency object that marks a starting
/// point. This could be a dialog window or a panel control that
/// hosts bound controls.</param>
/// <param name="properties">The properties to be updated if
/// <paramref name="obj"/> or one of its childs provide it along
/// with a binding expression.</param>
public static void UpdateBindingSources(DependencyObject obj,
params DependencyProperty[] properties)
{
foreach (DependencyProperty depProperty in properties)
{
//check whether the submitted object provides a bound property
//that matches the property parameters
BindingExpression be = BindingOperations.GetBindingExpression(obj, depProperty);
if (be != null) be.UpdateSource();
}
int count = VisualTreeHelper.GetChildrenCount(obj);
for (int i = 0; i < count; i++)
{
//process child items recursively
DependencyObject childObject = VisualTreeHelper.GetChild(obj, i);
UpdateBindingSources(childObject, properties);
}
}
#endregion
/// <summary>
/// Tries to locate a given item within the visual tree,
/// starting with the dependency object at a given position.
/// </summary>
/// <typeparam name="T">The type of the element to be found
/// on the visual tree of the element at the given location.</typeparam>
/// <param name="reference">The main element which is used to perform
/// hit testing.</param>
/// <param name="point">The position to be evaluated on the origin.</param>
public static T TryFindFromPoint<T>(UIElement reference, Point point)
where T : DependencyObject
{
DependencyObject element = reference.InputHitTest(point)
as DependencyObject;
if (element == null) return null;
else if (element is T) return (T)element;
else return TryFindParent<T>(element);
}
}
Usage
<!-- Drag and Drop Popup -->
<Popup x:Name="popup1"
AllowsTransparency="True"
IsHitTestVisible="False"
Placement="RelativePoint"
PlacementTarget="{Binding ElementName=shareGrid}">
<!-- Your own Popup construction Use properties of DraggedObject inside for Binding -->
<TextBlock Margin="8,0,0,0"
VerticalAlignment="Center"
FontSize="14"
FontWeight="Bold"
<!-- I used name property of in my Dragged row -->
Text="{Binding Path=Name}" />
</Popup>
<DataGrid x:Name="myDataGrid"
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserSortColumns="False"
ItemsSource="{Binding}"
SelectionMode="Single"
this:DragDropRowBehavior.Enabled="True"
this:DragDropRowBehavior.PopupControl="{Binding ElementName=popup1}"></DataGrid >
I've created a behavior out of #Dave solution:
public class DragDropRowBehavior : Behavior<DataGrid>
{
private object draggedItem;
private bool isEditing;
private bool isDragging;
#region DragEnded
public static readonly RoutedEvent DragEndedEvent =
EventManager.RegisterRoutedEvent("DragEnded", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DragDropRowBehavior));
public static void AddDragEndedHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie != null)
uie.AddHandler(DragDropRowBehavior.DragEndedEvent, handler);
}
public static void RemoveDragEndedHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie != null)
uie.RemoveHandler(DragDropRowBehavior.DragEndedEvent, handler);
}
private void RaiseDragEndedEvent()
{
var args = new RoutedEventArgs(DragDropRowBehavior.DragEndedEvent);
AssociatedObject.RaiseEvent(args);
}
#endregion
#region Popup
public static readonly DependencyProperty PopupProperty =
DependencyProperty.Register("Popup", typeof(System.Windows.Controls.Primitives.Popup), typeof(DragDropRowBehavior));
public System.Windows.Controls.Primitives.Popup Popup
{
get { return (System.Windows.Controls.Primitives.Popup)GetValue(PopupProperty); }
set { SetValue(PopupProperty, value); }
}
#endregion
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.BeginningEdit += OnBeginEdit;
AssociatedObject.CellEditEnding += OnEndEdit;
AssociatedObject.MouseLeftButtonUp += OnMouseLeftButtonUp;
AssociatedObject.PreviewMouseLeftButtonDown += OnMouseLeftButtonDown;
AssociatedObject.MouseMove += OnMouseMove;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.BeginningEdit -= OnBeginEdit;
AssociatedObject.CellEditEnding -= OnEndEdit;
AssociatedObject.MouseLeftButtonUp -= OnMouseLeftButtonUp;
AssociatedObject.MouseLeftButtonDown -= OnMouseLeftButtonDown;
AssociatedObject.MouseMove -= OnMouseMove;
Popup = null;
draggedItem = null;
isEditing = false;
isDragging = false;
}
private void OnBeginEdit(object sender, DataGridBeginningEditEventArgs e)
{
isEditing = true;
//in case we are in the middle of a drag/drop operation, cancel it...
if (isDragging) ResetDragDrop();
}
private void OnEndEdit(object sender, DataGridCellEditEndingEventArgs e)
{
isEditing = false;
}
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (isEditing) return;
var row = UIHelpers.TryFindFromPoint<DataGridRow>((UIElement)sender, e.GetPosition(AssociatedObject));
if (row == null || row.IsEditing) return;
//set flag that indicates we're capturing mouse movements
isDragging = true;
draggedItem = row.Item;
}
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (!isDragging || isEditing)
return;
//get the target item
var targetItem = AssociatedObject.SelectedItem;
if (targetItem == null || !ReferenceEquals(draggedItem, targetItem))
{
//get target index
var targetIndex = ((AssociatedObject).ItemsSource as IList).IndexOf(targetItem);
//remove the source from the list
((AssociatedObject).ItemsSource as IList).Remove(draggedItem);
//move source at the target's location
((AssociatedObject).ItemsSource as IList).Insert(targetIndex, draggedItem);
//select the dropped item
AssociatedObject.SelectedItem = draggedItem;
RaiseDragEndedEvent();
}
//reset
ResetDragDrop();
}
private void ResetDragDrop()
{
isDragging = false;
Popup.IsOpen = false;
AssociatedObject.IsReadOnly = false;
}
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (!isDragging || e.LeftButton != MouseButtonState.Pressed)
return;
Popup.DataContext = draggedItem;
//display the popup if it hasn't been opened yet
if (!Popup.IsOpen)
{
//switch to read-only mode
AssociatedObject.IsReadOnly = true;
//make sure the popup is visible
Popup.IsOpen = true;
}
var popupSize = new Size(Popup.ActualWidth, Popup.ActualHeight);
Popup.PlacementRectangle = new Rect(e.GetPosition(AssociatedObject), popupSize);
//make sure the row under the grid is being selected
var position = e.GetPosition(AssociatedObject);
var row = UIHelpers.TryFindFromPoint<DataGridRow>(AssociatedObject, position);
if (row != null) AssociatedObject.SelectedItem = row.Item;
}
}
Hope it helps the future users :)
if the event is not fired at the desired object, it is because some other control is "swallowing" it. Try to use OnPreviewMouseLeftButtonDown instead. If preview is not showing the event, try to move that preview method to a parent class.
My combobox returns a set of values from s stored procedure as this
private void BindCombo()
{
DataCombo.FillCombo(ComboDS(2313001), cmbClass, 0);
DataCombo.FillCombo(DDCombo(5007), cmbGroup, 0);
}
I managed to give a rudimentary auto complete suggestion as IsTextSearchenabled but cannot get a auto suggestion box that i would like.
I have seen loads of examples of autocomplete/suggestive textboxes but none of them seem to suit me.
this code apparently suits me.
but how would i use the auto suggest here
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace DotNetZen.AutoFilteredComboBox
{
public class AutoFilteredComboBox : ComboBox
{
private int silenceEvents = 0;
/// <summary>
/// Creates a new instance of <see cref="AutoFilteredComboBox" />.
/// </summary>
public AutoFilteredComboBox()
{
DependencyPropertyDescriptor textProperty = DependencyPropertyDescriptor.FromProperty(
ComboBox.TextProperty, typeof(AutoFilteredComboBox));
textProperty.AddValueChanged(this, this.OnTextChanged);
this.RegisterIsCaseSensitiveChangeNotification();
}
#region IsCaseSensitive Dependency Property
/// <summary>
/// The <see cref="DependencyProperty"/> object of the <see cref="IsCaseSensitive" /> dependency property.
/// </summary>
public static readonly DependencyProperty IsCaseSensitiveProperty =
DependencyProperty.Register("IsCaseSensitive", typeof(bool), typeof(AutoFilteredComboBox), new UIPropertyMetadata(false));
/// <summary>
/// Gets or sets the way the combo box treats the case sensitivity of typed text.
/// </summary>
/// <value>The way the combo box treats the case sensitivity of typed text.</value>
[System.ComponentModel.Description("The way the combo box treats the case sensitivity of typed text.")]
[System.ComponentModel.Category("AutoFiltered ComboBox")]
[System.ComponentModel.DefaultValue(true)]
public bool IsCaseSensitive
{
[System.Diagnostics.DebuggerStepThrough]
get
{
return (bool)this.GetValue(IsCaseSensitiveProperty);
}
[System.Diagnostics.DebuggerStepThrough]
set
{
this.SetValue(IsCaseSensitiveProperty, value);
}
}
protected virtual void OnIsCaseSensitiveChanged(object sender, EventArgs e)
{
if (this.IsCaseSensitive)
this.IsTextSearchEnabled = false;
this.RefreshFilter();
}
private void RegisterIsCaseSensitiveChangeNotification()
{
System.ComponentModel.DependencyPropertyDescriptor.FromProperty(IsCaseSensitiveProperty, typeof(AutoFilteredComboBox)).AddValueChanged(
this, this.OnIsCaseSensitiveChanged);
}
#endregion
#region DropDownOnFocus Dependency Property
/// <summary>
/// The <see cref="DependencyProperty"/> object of the <see cref="DropDownOnFocus" /> dependency property.
/// </summary>
public static readonly DependencyProperty DropDownOnFocusProperty =
DependencyProperty.Register("DropDownOnFocus", typeof(bool), typeof(AutoFilteredComboBox), new UIPropertyMetadata(true));
/// <summary>
/// Gets or sets the way the combo box behaves when it receives focus.
/// </summary>
/// <value>The way the combo box behaves when it receives focus.</value>
[System.ComponentModel.Description("The way the combo box behaves when it receives focus.")]
[System.ComponentModel.Category("AutoFiltered ComboBox")]
[System.ComponentModel.DefaultValue(true)]
public bool DropDownOnFocus
{
[System.Diagnostics.DebuggerStepThrough]
get
{
return (bool)this.GetValue(DropDownOnFocusProperty);
}
[System.Diagnostics.DebuggerStepThrough]
set
{
this.SetValue(DropDownOnFocusProperty, value);
}
}
#endregion
#region | Handle selection |
/// <summary>
/// Called when <see cref="ComboBox.ApplyTemplate()"/> is called.
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.EditableTextBox.SelectionChanged += this.EditableTextBox_SelectionChanged;
}
/// <summary>
/// Gets the text box in charge of the editable portion of the combo box.
/// </summary>
protected TextBox EditableTextBox
{
get
{
return ((TextBox)base.GetTemplateChild("PART_EditableTextBox"));
}
}
private int start = 0, length = 0;
private void EditableTextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
if (this.silenceEvents == 0)
{
this.start = ((TextBox)(e.OriginalSource)).SelectionStart;
this.length = ((TextBox)(e.OriginalSource)).SelectionLength;
this.RefreshFilter();
}
}
#endregion
#region | Handle focus |
/// <summary>
/// Invoked whenever an unhandled <see cref="UIElement.GotFocus" /> event
/// reaches this element in its route.
/// </summary>
/// <param name="e">The <see cref="RoutedEventArgs" /> that contains the event data.</param>
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
if (this.ItemsSource != null && this.DropDownOnFocus)
{
this.IsDropDownOpen = true;
}
}
#endregion
#region | Handle filtering |
private void RefreshFilter()
{
if (this.ItemsSource != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(this.ItemsSource);
view.Refresh();
this.IsDropDownOpen = true;
}
}
private bool FilterPredicate(object value)
{
// We don't like nulls.
if (value == null)
return false;
// If there is no text, there's no reason to filter.
if (this.Text.Length == 0)
return true;
string prefix = this.Text;
// If the end of the text is selected, do not mind it.
if (this.length > 0 && this.start + this.length == this.Text.Length)
{
prefix = prefix.Substring(0, this.start);
}
return value.ToString()
.StartsWith(prefix, !this.IsCaseSensitive, CultureInfo.CurrentCulture);
}
#endregion
/// <summary>
/// Called when the source of an item in a selector changes.
/// </summary>
/// <param name="oldValue">Old value of the source.</param>
/// <param name="newValue">New value of the source.</param>
protected override void OnItemsSourceChanged(System.Collections.IEnumerable oldValue, System.Collections.IEnumerable newValue)
{
if (newValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += this.FilterPredicate;
}
if (oldValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(oldValue);
view.Filter -= this.FilterPredicate;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
private void OnTextChanged(object sender, EventArgs e)
{
if (!this.IsTextSearchEnabled && this.silenceEvents == 0)
{
this.RefreshFilter();
// Manually simulate the automatic selection that would have been
// available if the IsTextSearchEnabled dependency property was set.
if (this.Text.Length > 0)
{
foreach (object item in CollectionViewSource.GetDefaultView(this.ItemsSource))
{
int text = item.ToString().Length, prefix = this.Text.Length;
this.SelectedItem = item;
this.silenceEvents++;
this.EditableTextBox.Text = item.ToString();
this.EditableTextBox.Select(prefix, text - prefix);
this.silenceEvents--;
break;
}
}
}
}
}
}
Also found the AutoFilteredComboBox to be very simple to work with. Though I have made some changes:
Removed use of DependencyPropertyDescriptor to avoid memory leak of combobox-objects
Introduced FilterItem-event and FilterList-event to allow one to customize the filtering
Changed default filtering from starts-with-string to contains-string
Removed support of having IsTextSearchEnabled enabled
Shows dropdown as soon one changes the search string, so search result is displayed
Example of how it is used:
<Controls:AutoFilteredComboBox ItemsSource="{Binding ViewModel.AvailableItems}"
SelectedValue="{Binding ViewModel.SelectedItem, Mode=TwoWay}"
IsEditable="True" IsTextSearchEnabled="False"/>
Improved version of AutoFilteredComboBox:
public class AutoFilteredComboBox : ComboBox
{
bool _ignoreTextChanged;
string _currentText;
/// <summary>
/// Creates a new instance of <see cref="AutoFilteredComboBox" />.
/// </summary>
public AutoFilteredComboBox()
{
if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this)) return;
}
public event Func<object, string, bool> FilterItem;
public event Action<string> FilterList;
#region IsCaseSensitive Dependency Property
/// <summary>
/// The <see cref="DependencyProperty"/> object of the <see cref="IsCaseSensitive" /> dependency property.
/// </summary>
public static readonly DependencyProperty IsCaseSensitiveProperty =
DependencyProperty.Register("IsCaseSensitive", typeof(bool), typeof(AutoFilteredComboBox), new UIPropertyMetadata(false));
/// <summary>
/// Gets or sets the way the combo box treats the case sensitivity of typed text.
/// </summary>
/// <value>The way the combo box treats the case sensitivity of typed text.</value>
[Description("The way the combo box treats the case sensitivity of typed text.")]
[Category("AutoFiltered ComboBox")]
[DefaultValue(true)]
public bool IsCaseSensitive
{
[System.Diagnostics.DebuggerStepThrough]
get
{
return (bool)this.GetValue(IsCaseSensitiveProperty);
}
[System.Diagnostics.DebuggerStepThrough]
set
{
this.SetValue(IsCaseSensitiveProperty, value);
}
}
#endregion
#region DropDownOnFocus Dependency Property
/// <summary>
/// The <see cref="DependencyProperty"/> object of the <see cref="DropDownOnFocus" /> dependency property.
/// </summary>
public static readonly DependencyProperty DropDownOnFocusProperty =
DependencyProperty.Register("DropDownOnFocus", typeof(bool), typeof(AutoFilteredComboBox), new UIPropertyMetadata(false));
/// <summary>
/// Gets or sets the way the combo box behaves when it receives focus.
/// </summary>
/// <value>The way the combo box behaves when it receives focus.</value>
[Description("The way the combo box behaves when it receives focus.")]
[Category("AutoFiltered ComboBox")]
[DefaultValue(false)]
public bool DropDownOnFocus
{
[System.Diagnostics.DebuggerStepThrough]
get
{
return (bool)this.GetValue(DropDownOnFocusProperty);
}
[System.Diagnostics.DebuggerStepThrough]
set
{
this.SetValue(DropDownOnFocusProperty, value);
}
}
#endregion
#region | Handle focus |
/// <summary>
/// Invoked whenever an unhandled <see cref="UIElement.GotFocus" /> event
/// reaches this element in its route.
/// </summary>
/// <param name="e">The <see cref="RoutedEventArgs" /> that contains the event data.</param>
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
if (this.ItemsSource != null && this.DropDownOnFocus)
{
this.IsDropDownOpen = true;
}
}
#endregion
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
AddHandler(TextBox.TextChangedEvent, new TextChangedEventHandler(OnTextChanged));
KeyUp += AutoFilteredComboBox_KeyUp;
this.IsTextSearchEnabled = false;
}
void AutoFilteredComboBox_KeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Down)
{
if (this.IsDropDownOpen == true)
{
// Ensure that focus is given to the dropdown list
if (Keyboard.FocusedElement is TextBox)
{
Keyboard.Focus(this);
if (this.Items.Count > 0)
{
if (this.SelectedIndex == -1 || this.SelectedIndex==0)
this.SelectedIndex = 0;
}
}
}
}
if (Keyboard.FocusedElement is TextBox)
{
if (e.OriginalSource is TextBox)
{
// Avoid the automatic selection of the first letter (As next letter will cause overwrite)
TextBox textBox = e.OriginalSource as TextBox;
if (textBox.Text.Length == 1 && textBox.SelectionLength == 1)
{
textBox.SelectionLength = 0;
textBox.SelectionStart = 1;
}
}
}
}
#region | Handle filtering |
private void RefreshFilter()
{
if (this.ItemsSource != null)
{
Action<string> filterList = FilterList;
if (filterList != null)
{
filterList(_currentText);
}
else
{
ICollectionView view = CollectionViewSource.GetDefaultView(this.ItemsSource);
view.Refresh();
}
this.SelectedIndex = -1; // Prepare so arrow down selects first
this.IsDropDownOpen = true;
}
}
private bool FilterPredicate(object value)
{
// We don't like nulls.
if (value == null)
return false;
// If there is no text, there's no reason to filter.
if (string.IsNullOrEmpty(_currentText))
return true;
Func<object, string, bool> filterItem = FilterItem;
if (filterItem != null)
return filterItem(value, _currentText);
if (IsCaseSensitive)
return value.ToString().Contains(_currentText);
else
return value.ToString().ToUpper().Contains(_currentText.ToUpper());
}
#endregion
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
try
{
_ignoreTextChanged = true; // Ignore the following TextChanged
base.OnSelectionChanged(e);
}
finally
{
_ignoreTextChanged = false;
}
}
/// <summary>
/// Called when the source of an item in a selector changes.
/// </summary>
/// <param name="oldValue">Old value of the source.</param>
/// <param name="newValue">New value of the source.</param>
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
if (newValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
if (FilterList == null)
view.Filter += this.FilterPredicate;
}
if (oldValue != null)
{
ICollectionView view = CollectionViewSource.GetDefaultView(oldValue);
view.Filter -= this.FilterPredicate;
}
base.OnItemsSourceChanged(oldValue, newValue);
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
if (_ignoreTextChanged)
return;
_currentText = Text;
if (!this.IsTextSearchEnabled)
{
this.RefreshFilter();
}
}
I have found a super simple workaround to my problem.
i created a preview text input event of the combobox.
then i just wrote
Combobox.IsDropDownOpen = true
may not the most elegant but works in my case