I have a WPF app that has a ListBox. The drag mechanism is already implemented, but when the list is too long and I want to move an item to a position not visible, I can't.
For example, the screen shows 10 items. And I have 20 items. If I want to drag the last item to the first position I must drag to the top and drop. Scroll up and drag again.
How can I make the ListBox auto scroll?
Got it. Used the event DragOver of the ListBox, used the function found here to get the scrollviewer of the listbox and after that its just a bit of juggling with the Position.
private void ItemsList_DragOver(object sender, System.Windows.DragEventArgs e)
{
ListBox li = sender as ListBox;
ScrollViewer sv = FindVisualChild<ScrollViewer>(ItemsList);
double tolerance = 10;
double verticalPos = e.GetPosition(li).Y;
double offset = 3;
if (verticalPos < tolerance) // Top of visible list?
{
sv.ScrollToVerticalOffset(sv.VerticalOffset - offset); //Scroll up.
}
else if (verticalPos > li.ActualHeight - tolerance) //Bottom of visible list?
{
sv.ScrollToVerticalOffset(sv.VerticalOffset + offset); //Scroll down.
}
}
public static childItem FindVisualChild<childItem>(DependencyObject obj) where childItem : DependencyObject
{
// Search immediate children first (breadth-first)
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
return (childItem)child;
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
Based on this I have created an Attached Behavior which can easily be used like this -
<ListView
xmlns:WpfExtensions="clr-namespace:WpfExtensions"
WpfExtensions:DragDropExtension.ScrollOnDragDrop="True"
Here is the code for attached behavior -
/// <summary>
/// Provides extended support for drag drop operation
/// </summary>
public static class DragDropExtension
{
public static readonly DependencyProperty ScrollOnDragDropProperty =
DependencyProperty.RegisterAttached("ScrollOnDragDrop",
typeof(bool),
typeof(DragDropExtension),
new PropertyMetadata(false, HandleScrollOnDragDropChanged));
public static bool GetScrollOnDragDrop(DependencyObject element)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
return (bool)element.GetValue(ScrollOnDragDropProperty);
}
public static void SetScrollOnDragDrop(DependencyObject element, bool value)
{
if (element == null)
{
throw new ArgumentNullException("element");
}
element.SetValue(ScrollOnDragDropProperty, value);
}
private static void HandleScrollOnDragDropChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement container = d as FrameworkElement;
if (d == null)
{
Debug.Fail("Invalid type!");
return;
}
Unsubscribe(container);
if (true.Equals(e.NewValue))
{
Subscribe(container);
}
}
private static void Subscribe(FrameworkElement container)
{
container.PreviewDragOver += OnContainerPreviewDragOver;
}
private static void OnContainerPreviewDragOver(object sender, DragEventArgs e)
{
FrameworkElement container = sender as FrameworkElement;
if (container == null)
{
return;
}
ScrollViewer scrollViewer = GetFirstVisualChild<ScrollViewer>(container);
if (scrollViewer == null)
{
return;
}
double tolerance = 60;
double verticalPos = e.GetPosition(container).Y;
double offset = 20;
if (verticalPos < tolerance) // Top of visible list?
{
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - offset); //Scroll up.
}
else if (verticalPos > container.ActualHeight - tolerance) //Bottom of visible list?
{
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + offset); //Scroll down.
}
}
private static void Unsubscribe(FrameworkElement container)
{
container.PreviewDragOver -= OnContainerPreviewDragOver;
}
private static T GetFirstVisualChild<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
return (T)child;
}
T childItem = GetFirstVisualChild<T>(child);
if (childItem != null)
{
return childItem;
}
}
}
return null;
}
}
Related
This is my xaml code, a NavigationView contains a NavigationView.HeaderTemplate.
<NavigationView.HeaderTemplate>
<DataTemplate
x:Name="HeaderDataTemplate">
<TextBlock
Name="HeaderText"
Text="I am a TextBlock"
Margin="0,0,0,10"/>
</DataTemplate>
</NavigationView.HeaderTemplate>
I want to know how to get the Texkblock through c# code.
Here is my c# code bu it doesn't work.
private void nvSample_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
var navView = sender as NavigationView;
NavigationViewItem item = args.SelectedItemContainer as NavigationViewItem;
if(item.Tag.ToString() == "SatrtPage")
{
DataTemplate dataTemplate = sender.HeaderTemplate;
DependencyObject dio = dataTemplate.LoadContent();
var rectangle= FindVisualChild<TextBlock>(dio);
if (rectangle != null)
{
rectangle.Text = "LLLLLLL";
}
contentFrame.Navigate(typeof(StartPage));
}
private childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is childItem)
{
return (childItem)child;
}
else
{
childItem childOfChild = FindVisualChild<childItem>(child);
if (childOfChild != null)
return childOfChild;
}
}
return null;
}
Try this:
private void nvSample_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args)
{
var navView = sender as NavigationView;
if (FindVisualChild<SplitView>(navView) is SplitView splitView
&& splitView.Content is Panel panel
&& panel.Children.OfType<ContentControl>().FirstOrDefault() is ContentControl cc)
{
var textBlock = FindVisualChild<TextBlock>(cc);
if (textBlock != null)
textBlock.Text = "changed text...";
}
}
I have this UserControl:
[ContentProperty("Items")]
[DefaultProperty("Items")]
public partial class PanelControl : UserControl
{
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(PanelControl), new FrameworkPropertyMetadata(Orientation.Horizontal, new PropertyChangedCallback(OnOrientationChanged)));
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<UIElement>), typeof(PanelControl), new FrameworkPropertyMetadata(new ObservableCollection<UIElement>(), new PropertyChangedCallback(OnItemsChanged)));
public static readonly DependencyProperty SizeProperty = DependencyProperty.RegisterAttached("Size", typeof(double), typeof(PanelControl), new FrameworkPropertyMetadata(1.0, new PropertyChangedCallback(OnSizeChanged)), new ValidateValueCallback(IsSizeValid));
public Orientation Orientation
{
get
{
return (Orientation)GetValue(OrientationProperty);
}
set
{
SetValue(OrientationProperty, value);
}
}
public ObservableCollection<UIElement> Items
{
get
{
return (ObservableCollection<UIElement>)GetValue(ItemsProperty);
}
set
{
SetValue(ItemsProperty, value);
}
}
public static void SetSize(UIElement element, double size)
{
element.SetValue(SizeProperty, size);
}
public static double GetSize(UIElement element)
{
return (double)element.GetValue(SizeProperty);
}
private static void OnOrientationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
/*MessageBox.Show("orientation");*/
((PanelControl)dependencyObject).ClearAndBuildGrid();
}
private static void OnItemsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
/*MessageBox.Show("items");*/
((PanelControl)dependencyObject).ClearAndBuildGrid();
if(args.OldValue != null)
((ObservableCollection<UIElement>)args.OldValue).CollectionChanged -= ((PanelControl)dependencyObject).OnItemsCollectionChanged;
if (args.NewValue != null)
((ObservableCollection<UIElement>)args.NewValue).CollectionChanged += ((PanelControl)dependencyObject).OnItemsCollectionChanged;
}
private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
/*MessageBox.Show("collection");*/
ClearAndBuildGrid();
}
private static void OnSizeChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
((PanelControl)dependencyObject).ClearAndBuildGrid();
/*MessageBox.Show("size");*/
}
private static bool IsSizeValid(object value)
{
return (double)value < 0 ? false : true;
}
private void ClearAndBuildGrid()
{
MainGrid.Children.Clear();
MainGrid.RowDefinitions.Clear();
MainGrid.ColumnDefinitions.Clear();
/*MessageBox.Show(MainGrid.Children.Count.ToString());*/
for (int i = 0; i < Items.Count; i++)
{
if (Orientation == Orientation.Horizontal)
{
MainGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = SizeToGridLength(GetSize(Items[i])) });
Grid.SetColumn(Items[i], i * 2);
if (i != Items.Count - 1)
{
MainGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(5) });
GridSplitter splitter = new GridSplitter() { ResizeDirection = GridResizeDirection.Columns, HorizontalAlignment = HorizontalAlignment.Stretch };
Grid.SetColumn(splitter, i * 2 + 1);
MainGrid.Children.Add(splitter);
}
}
else
{
MainGrid.RowDefinitions.Add(new RowDefinition() { Height = SizeToGridLength(GetSize(Items[i])) });
Grid.SetRow(Items[i], i * 2);
if (i != Items.Count - 1)
{
MainGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(5) });
GridSplitter splitter = new GridSplitter() { ResizeDirection = GridResizeDirection.Rows, VerticalAlignment = VerticalAlignment.Stretch };
Grid.SetRow(splitter, i * 2 + 1);
MainGrid.Children.Add(splitter);
}
}
MainGrid.Children.Add(Items[i]);
}
}
private GridLength SizeToGridLength(double size)
{
return new GridLength(size, GridUnitType.Star);
}
public PanelControl()
{
InitializeComponent();
Items.CollectionChanged += OnItemsCollectionChanged;
}
}
And I use it here:
<p:PanelControl>
<Button />
<Button />
<Button />
</p:PanelControl>
When i start application it works good, but in designer I have underlined first button in xaml and error "Specified element is already the logical child of another element. Disconnect it first." Thanks for help, sorry for my bad English.
Not sure what is going on with the designer but this will 'fix' it.
Change the line:
MainGrid.Children.Add(Items[i]);
To:
var parent = VisualTreeHelper.GetParent(Items[i]) as Grid;
if (parent != null)
parent.Children.Remove(Items[i]);
MainGrid.Children.Add(Items[i]);
Using the code from J.H., I would move it up to the top of your function, so that the state of your grid when you clear it, is "cleared", and all the children have been disconnected from the Visual Tree.
private void ClearAndBuildGrid()
{
foreach (var item in Items)
{
var parent = System.Windows.Media.VisualTreeHelper.GetParent(item) as Grid;
if (parent != null)
parent.Children.Remove(item);
}
MainGrid.Children.Clear();
It would be a style/intention/preference thing, and to be clear, the answer J.H. gives is totally valid.
Consider using foreach instead of for and not having to deal with array subscripts.
I have a case where I want to minimize the horizontal padding of a textbox.
Using snoop I found that the textbox consists of a multiple sub-controls.
One of them is a TextBoxView with a margin of 2,0,2,0
The TextBoxView is an internal wpf component and has no public API.
How would you approach getting rid of the "internal padding"??
Set the outer margin to -2,0,-2,0 to compensate for the padding.
I created a custom control that removes that internal padding.
public class MyTextBox : TextBox
{
public MyTextBox()
{
Loaded += OnLoaded;
}
void OnLoaded(object sender, RoutedEventArgs e)
{
// the internal TextBoxView has a margin of 2,0,2,0 that needs to be removed
var contentHost = Template.FindName("PART_ContentHost", this) as ScrollViewer;
if (contentHost != null && contentHost.Content != null && contentHost.Content is FrameworkElement)
{
var textBoxView = contentHost.Content as FrameworkElement;
textBoxView.Margin = new Thickness(0,0,0,0);
}
}
}
Here is a dirty way of doing it:
public static class TextBoxView
{
public static readonly DependencyProperty MarginProperty = DependencyProperty.RegisterAttached(
"Margin",
typeof(Thickness?),
typeof(TextBoxView),
new PropertyMetadata(null, OnTextBoxViewMarginChanged));
public static void SetMargin(TextBox element, Thickness? value)
{
element.SetValue(MarginProperty, value);
}
[AttachedPropertyBrowsableForChildren(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static Thickness? GetMargin(TextBox element)
{
return (Thickness?)element.GetValue(MarginProperty);
}
private static void OnTextBoxViewMarginChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBox = (TextBox)d;
OnTextBoxViewMarginChanged(textBox, (Thickness?)e.NewValue);
}
private static void OnTextBoxViewMarginChanged(TextBox textBox, Thickness? margin)
{
if (!textBox.IsLoaded)
{
textBox.Dispatcher.BeginInvoke(
DispatcherPriority.Loaded,
new Action(() => OnTextBoxViewMarginChanged(textBox, margin)));
return;
}
var textBoxView = textBox.NestedChildren()
.SingleOrDefault(x => x.GetType().Name == "TextBoxView");
if (margin == null)
{
textBoxView?.ClearValue(FrameworkElement.MarginProperty);
}
else
{
textBoxView?.SetValue(FrameworkElement.MarginProperty, margin);
}
}
private static IEnumerable<DependencyObject> NestedChildren(this DependencyObject parent)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
yield return child;
if (VisualTreeHelper.GetChildrenCount(child) == 0)
{
continue;
}
foreach (var nestedChild in NestedChildren(child))
{
yield return nestedChild;
}
}
}
}
It allows setting the margin on textboxes:
<Style TargetType="{x:Type TextBox}">
<Setter Property="demo:TextBoxView.Margin" Value="1,0" />
</Style>
Not optimized for performance at all.
I recently asked a question on how to reorder an ItemsControl using Drag and Drop (ItemsControl Drag and Drop). The answer worked great for the time being (code below) but now I am trying to implement MVVM and the current solution requires access to items in the view. Any ideas how to change this to work with MVVM? I plan to make attached properties to bind to commands but I don't know how to get rid of lines such as: int index = (int)(e.GetPosition(DimsContainer).X / width);
Current drag and drop code:
private void DimsContainer_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
_isDown = true;
_startPoint = e.GetPosition(this.DimsContainer);
}
private void DimsContainer_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
_isDown = false;
_isDragging = false;
if (_realDragSource != null)
{
_realDragSource.ReleaseMouseCapture();
}
}
private void DimsContainer_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (_isDown)
{
if ((_isDragging == false) &&
((Math.Abs(e.GetPosition(this.DimsContainer).X - _startPoint.X) >
SystemParameters.MinimumHorizontalDragDistance) ||
(Math.Abs(e.GetPosition(this.DimsContainer).Y - _startPoint.Y) >
SystemParameters.MinimumVerticalDragDistance)))
{
_isDragging = true;
_realDragSource = e.Source as UIElement;
_realDragSource.CaptureMouse();
double width = ((FrameworkElement)(this.DimsContainer.ItemContainerGenerator.ContainerFromIndex(0))).ActualWidth;
int index = (int)(e.GetPosition(DimsContainer).X / width);
DragDrop.DoDragDrop(_realDragSource, new DataObject("UIElement", index, true), DragDropEffects.Move);
}
}
}
private void DimsContainer_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent("UIElement"))
{
e.Effects = DragDropEffects.Move;
}
}
private void DimsContainer_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent("UIElement"))
{
int sourceIndex = (int)e.Data.GetData("UIElement");
int removedObject = this.indices[sourceIndex];
this.indices.RemoveAt(sourceIndex);
UIElement droptarget = e.Source as UIElement;
// If a drag/drop is happening, there is definitely
// one child in the DimsContainer
double width = ((FrameworkElement)(this.DimsContainer.ItemContainerGenerator.ContainerFromIndex(0))).ActualWidth;
int index = (int)(e.GetPosition(DimsContainer).X / width);
try
{
this.indices.Insert(index, removedObject);
}
catch (InvalidOperationException)
{
// ignore
}
_isDown = false;
_isDragging = false;
_realDragSource.ReleaseMouseCapture();
}
}
public static int RemoveItemFromItemsControl(ItemsControl itemsControl, object itemToRemove)
{
int indexToBeRemoved = -1;
if (itemToRemove != null)
{
indexToBeRemoved = itemsControl.Items.IndexOf(itemToRemove);
if (indexToBeRemoved != -1)
{
IEnumerable itemsSource = itemsControl.ItemsSource;
if (itemsSource == null)
{
itemsControl.Items.RemoveAt(indexToBeRemoved);
}
// Is the ItemsSource IList or IList<T>? If so, remove the item from the list.
else if (itemsSource is IList)
{
((IList)itemsSource).RemoveAt(indexToBeRemoved);
}
else
{
Type type = itemsSource.GetType();
Type genericIListType = type.GetInterface("IList`1");
if (genericIListType != null)
{
type.GetMethod("RemoveAt").Invoke(itemsSource, new object[] { indexToBeRemoved });
}
}
}
}
return indexToBeRemoved;
}
public static void InsertItemInItemsControl(ItemsControl itemsControl, object itemToInsert, int insertionIndex)
{
if (itemToInsert != null)
{
IEnumerable itemsSource = itemsControl.ItemsSource;
if (itemsSource == null)
{
itemsControl.Items.Insert(insertionIndex, itemToInsert);
}
// Is the ItemsSource IList or IList<T>? If so, insert the dragged item in the list.
else if (itemsSource is IList)
{
((IList)itemsSource).Insert(insertionIndex, itemToInsert);
}
else
{
Type type = itemsSource.GetType();
Type genericIListType = type.GetInterface("IList`1");
if (genericIListType != null)
{
type.GetMethod("Insert").Invoke(itemsSource, new object[] { insertionIndex, itemToInsert });
}
}
}
}
use drag and drop behavior. e.g. http://www.codeproject.com/KB/WPF/gong-wpf-dragdrop-ii.aspx
I have a StackPanel containing a number ofTextBox. Is there a way to get the Next / Previous visual elements ?
The functionality I want is fairly analogous to jQuery's .next() function that get the next object.
You can try following method to enumerate the Visual Tree.
public static IEnumerable<T> FindVisualChildren<T>
(DependencyObject depObj, string childName) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
var frameworkElement = child as FrameworkElement;
if (child != null && frameworkElement.Name == childName)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child, childName))
{
yield return childOfChild;
}
}
}
}
Assuming that you named your textBoxes "tbInsideStackPanel", use it like:
foreach (var textBox in FindVisualChildren<TextBox>(this.stackPanel1,
"tbInsideStackPanel").ToList())
{
textBox.Background = Brushes.Blue;
}