ListBox List Rotation - wpf

I want to create a ListBox that basically displays a circular list. For example a list with three items:
1
2
3 <- (Selector on item 3)
will rotate the list and have the selector over item 1 after passing item 3:
2
3
1 <- (Selector on item 1)
This is similar to the menu paradigm of the Windows Media Center.
What part of the ListBox should I be tweaking???

Well, I updated the Items... not sure it's the right way, but, anyhow, if you want to take a look at my implementation... here it is:
using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Collections.ObjectModel;
using ScrollLimit;
using System.Windows.Controls.Primitives;
using System.Text.RegularExpressions;
namespace ScrollLimit
{
public enum ScrollLimitBehaviour { Springs, Circular };
}
namespace SmoothScroll
{
[TemplatePart(Name = "PART_Border", Type = typeof(Border))]
[TemplatePart(Name = "PART_ScrollViewer", Type = typeof(ScrollViewer))]
[TemplatePart(Name = "PART_StackPanel", Type = typeof(StackPanel))]
public class SmoothScrollViewer : ListBox
{
private double move_init, scroll_init, scroll_offset, scroll_t0, scroll_span, scroll_v0, scroll_offset0, backOffset, adjustLimit, stopWindowCoord, hitOffset;
private bool scroll_mouseDown = false, scroll_direction = false, scroll_out = false, scrollLessThanZero = false, scrollMoreThanEnd = false, selectionAllowed = false, canUpdate = true, external = true;
private DispatcherTimer scroll_timerClock = new DispatcherTimer();
private EventHandler decelerateEventHandler, adjustEventHandler;
private int selectedIndex;
private ScrollViewer sv;
private Rectangle stopWindow;
private StackPanel sp;
private ListBoxItem lbi;
private Grid g;
private TextBox filterEdit;
//proprietatea de orientare a listei
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation",
typeof(Orientation),
typeof(SmoothScrollViewer),
new UIPropertyMetadata(Orientation.Vertical,
new PropertyChangedCallback(OnOrientationChanged),
new CoerceValueCallback(OnCoerceOrientation))
);
private static object OnCoerceOrientation(DependencyObject o, Object value)
{
SmoothScrollViewer ssw = o as SmoothScrollViewer;
if (ssw != null) return ssw.OnCoerceOrientation((Orientation)value);
else return value;
}
private static void OnOrientationChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SmoothScrollViewer ssw = o as SmoothScrollViewer;
if (ssw != null) ssw.OnOrientationChanged((Orientation)e.OldValue, (Orientation)e.NewValue);
}
protected virtual Orientation OnCoerceOrientation(Orientation value)
{
return value;
}
protected virtual void OnOrientationChanged(Orientation oldValue, Orientation newValue)
{
}
public Orientation Orientation
{
get
{
return (Orientation)GetValue(OrientationProperty);
}
set
{
SetValue(OrientationProperty, value);
}
}
//proprietatea de multiplicator de deplasament a chenarului de oprire a elementului evidentiat
public static readonly DependencyProperty StopWindowOffsetProperty =
DependencyProperty.Register("StopWindowOffset",
typeof(double),
typeof(SmoothScrollViewer),
new UIPropertyMetadata(-1.0,
new PropertyChangedCallback(OnStopWindowOffsetChanged),
new CoerceValueCallback(OnCoerceStopWindowOffset))
);
private static object OnCoerceStopWindowOffset(DependencyObject o, Object value)
{
SmoothScrollViewer ssw = o as SmoothScrollViewer;
if (ssw != null) return ssw.OnCoerceStopWindowOffset((double)value);
else return value;
}
private static void OnStopWindowOffsetChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SmoothScrollViewer ssw = o as SmoothScrollViewer;
if (ssw != null) ssw.OnStopWindowOffsetChanged((double)e.OldValue, (double)e.NewValue);
}
protected virtual double OnCoerceStopWindowOffset(double value)
{
return value;
}
protected virtual void OnStopWindowOffsetChanged(double oldValue, double newValue)
{
}
public double StopWindowOffset
{
get
{
return (double)GetValue(StopWindowOffsetProperty);
}
set
{
SetValue(StopWindowOffsetProperty, value);
}
}
//proprietatea de comportament la capete
public static readonly DependencyProperty ScrollLimitBehaviourProperty =
DependencyProperty.Register("ScrollLimitBehaviour",
typeof(ScrollLimitBehaviour),
typeof(SmoothScrollViewer),
new UIPropertyMetadata(ScrollLimitBehaviour.Springs,
new PropertyChangedCallback(OnScrollLimitBehaviourChanged),
new CoerceValueCallback(OnCoerceScrollLimitBehaviour))
);
private static object OnCoerceScrollLimitBehaviour(DependencyObject o, Object value)
{
SmoothScrollViewer ssw = o as SmoothScrollViewer;
if (ssw != null) return ssw.OnCoerceScrollLimitBehaviour((ScrollLimitBehaviour)value);
else return value;
}
private static void OnScrollLimitBehaviourChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SmoothScrollViewer ssw = o as SmoothScrollViewer;
if (ssw != null) ssw.OnScrollLimitBehaviourChanged((ScrollLimitBehaviour)e.OldValue, (ScrollLimitBehaviour)e.NewValue);
}
protected virtual ScrollLimitBehaviour OnCoerceScrollLimitBehaviour(ScrollLimitBehaviour value)
{
return value;
}
protected virtual void OnScrollLimitBehaviourChanged(ScrollLimitBehaviour oldValue, ScrollLimitBehaviour newValue)
{
}
public ScrollLimitBehaviour ScrollLimitBehaviour
{
get
{
return (ScrollLimitBehaviour)GetValue(ScrollLimitBehaviourProperty);
}
set
{
SetValue(ScrollLimitBehaviourProperty, value);
}
}
//proprietatea de existenta a filtrarii
public static readonly DependencyProperty CanFilterProperty =
DependencyProperty.Register("CanFilter",
typeof(bool),
typeof(SmoothScrollViewer),
new UIPropertyMetadata(false,
new PropertyChangedCallback(OnCanFilterChanged),
new CoerceValueCallback(OnCoerceCanFilter))
);
private static object OnCoerceCanFilter(DependencyObject o, Object value)
{
SmoothScrollViewer ssw = o as SmoothScrollViewer;
if (ssw != null) return ssw.OnCoerceCanFilter((bool)value);
else return value;
}
private static void OnCanFilterChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
SmoothScrollViewer ssw = o as SmoothScrollViewer;
if (ssw != null) ssw.OnCanFilterChanged((bool)e.OldValue, (bool)e.NewValue);
}
protected virtual bool OnCoerceCanFilter(bool value)
{
return value;
}
protected virtual void OnCanFilterChanged(bool oldValue, bool newValue)
{
}
public bool CanFilter
{
get
{
return (bool)GetValue(CanFilterProperty);
}
set
{
SetValue(CanFilterProperty, value);
}
}
//previne scroll-ul prin drag in afara listei
protected override void OnMouseMove(MouseEventArgs e)
{
}
protected override void OnIsMouseCapturedChanged(DependencyPropertyChangedEventArgs e)
{
}
//copie ItemsSource in Items
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
base.OnItemsSourceChanged(oldValue, newValue);
if (ItemsSource != null)
{
ObservableCollection<object> temp = new ObservableCollection<object>();
foreach (object o in ItemsSource) temp.Add(o);
ItemsSource = null;
for (int i = 0; i < temp.Count; i++) Items.Add(temp[i]);
}
}
//returneaza elementul primit ca parametru ca ListBoxItem
private ListBoxItem getListBoxItem(UIElement element)
{
if (element is ListBoxItem) return element as ListBoxItem;
while (element != this && element != null)
{
element = VisualTreeHelper.GetParent(element) as UIElement;
if (element is ListBoxItem) return element as ListBoxItem;
}
return null;
}
//la aplicarea sablonului vizual au loc asocierile de evenimente si initializarile
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
sv = GetTemplateChild("PART_ScrollViewer") as ScrollViewer;
sp = GetTemplateChild("PART_StackPanel") as StackPanel;
g = GetTemplateChild("PART_Grid") as Grid;
if ((ScrollBarVisibility)GetValue(ScrollViewer.HorizontalScrollBarVisibilityProperty) == ScrollBarVisibility.Disabled) sv.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
if ((ScrollBarVisibility)GetValue(ScrollViewer.VerticalScrollBarVisibilityProperty) == ScrollBarVisibility.Disabled) sv.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden;
sp.PreviewMouseDown += new MouseButtonEventHandler(l_MouseDown);
sp.PreviewMouseMove += new MouseEventHandler(l_MouseMove);
sp.PreviewMouseUp += new MouseButtonEventHandler(l_MouseUp);
sp.MouseLeave += new MouseEventHandler(l_MouseLeave);
sp.PreviewMouseWheel += new MouseWheelEventHandler(l_PreviewMouseWheel);
sp.PreviewKeyDown += new KeyEventHandler(sp_PreviewKeyDown);
if (Orientation == Orientation.Vertical)
{
sv.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
HorizontalContentAlignment = HorizontalAlignment.Stretch;
}
else
{
sv.VerticalScrollBarVisibility = ScrollBarVisibility.Disabled;
VerticalContentAlignment = VerticalAlignment.Stretch;
}
scroll_timerClock.Interval = new TimeSpan(0, 0, 0, 0, 1);
decelerateEventHandler = new EventHandler(scroll_timerClock_Tick);
adjustEventHandler = new EventHandler(adjust_timerClock_Tick);
scroll_timerClock.Tick += decelerateEventHandler;
LayoutUpdated += new EventHandler(l_LayoutUpdated);
PreviewMouseDown += new MouseButtonEventHandler(SmoothScrollViewer_MouseDown);
}
//prin interactiunea cu mouse-ul asupra listei, se initiaza o actiune de
//selectie non-externa
void SmoothScrollViewer_MouseDown(object sender, MouseButtonEventArgs e)
{
external = false;
}
//functie de scroll la element specificat
public void ScrollTo(object item)
{
if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
int indexOfItem = Items.IndexOf(item);
int nrItemsToMove = 0,
i = 0;
double cummulativeSize = 0;
do
{
lbi = (ListBoxItem)(ItemContainerGenerator.ContainerFromIndex(i));
cummulativeSize += (Orientation == Orientation.Vertical) ? lbi.ActualHeight : lbi.ActualWidth;
i++;
nrItemsToMove++;
}
while (cummulativeSize <= ((Orientation == Orientation.Vertical) ? sv.ViewportHeight : sv.ViewportWidth));
if (indexOfItem > Items.Count - nrItemsToMove || indexOfItem == 0)
{
for (i = 0; i <= nrItemsToMove; i++)
{
object elt = Items[0];
Items.RemoveAt(0);
Items.Add(elt);
}
indexOfItem = Items.IndexOf(item);
}
double scrollAmount = 0;
for (i = 0; i < indexOfItem - 1; i++)
{
lbi = (ListBoxItem)(ItemContainerGenerator.ContainerFromIndex(i));
scrollAmount += (Orientation == Orientation.Vertical) ? lbi.ActualHeight : lbi.ActualWidth;
}
if (Orientation == Orientation.Vertical) sv.ScrollToVerticalOffset(scrollAmount);
else sv.ScrollToHorizontalOffset(scrollAmount);
selectionAllowed = true;
SelectedItem = item;
selectionAllowed = false;
scroll_out = false;
}
}
//Manipularea listei din tastele sageti
void sp_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (((e.Key == Key.Right || e.Key == Key.Left) && Orientation == Orientation.Horizontal) || ((e.Key == Key.Up || e.Key == Key.Down) && Orientation == Orientation.Vertical))
{
adjustLimit = 0;
scrollLessThanZero = false;
scrollMoreThanEnd = false;
SelectedIndex = -1;
backOffset = ((Orientation == Orientation.Vertical) ? (sv.ExtentHeight - sv.ViewportHeight) : (sv.ExtentWidth - sv.ViewportWidth));
scroll_mouseDown = false;
scroll_v0 = (e.Key == Key.Right || e.Key == Key.Up ? -1 : 1);
scroll_t0 = Environment.TickCount;
scroll_direction = (scroll_v0 >= 0);
scroll_offset0 = ((Orientation == Orientation.Vertical) ? sv.VerticalOffset - sp.Margin.Top : sv.HorizontalOffset - sp.Margin.Left);
scroll_timerClock.IsEnabled = true;
}
else
if (e.Key == Key.Return && stopWindow == null)
{
UIElement elt = InputHitTest(new Point(ActualWidth / 2, ActualHeight / 2)) as UIElement;
if (elt != null)
{
lbi = getListBoxItem(elt);
selectionAllowed = true;
SelectedIndex = ItemContainerGenerator.IndexFromContainer(lbi);
}
}
e.Handled = true;
}
//la ficare actualizare a randarii controlului, se initializeaza/redimensioneaza chenarul
//de oprire a elementului selectabil si campul editabil de filtrare in cazul in care acesta
//exista
void l_LayoutUpdated(object sender, EventArgs e)
{
try
{
if (StopWindowOffset >= 0)
{
lbi = (ListBoxItem)(ItemContainerGenerator.ContainerFromIndex(((SelectedIndex > 0) ? SelectedIndex : selectedIndex)));
hitOffset = (Orientation == Orientation.Vertical) ? lbi.ActualHeight / 4 : lbi.ActualWidth / 4;
stopWindowCoord = StopWindowOffset * ((Orientation == Orientation.Vertical) ? lbi.ActualHeight : lbi.ActualWidth);
if (stopWindow == null)
{
stopWindow = new Rectangle();
stopWindow.SetValue(Grid.RowProperty, 1);
stopWindow.HorizontalAlignment = HorizontalAlignment.Left;
stopWindow.VerticalAlignment = VerticalAlignment.Top;
stopWindow.Fill = BorderBrush;
stopWindow.Opacity = 0.5;
stopWindow.IsHitTestVisible = false;
g.Children.Add(stopWindow);
}
stopWindow.Margin = (Orientation == Orientation.Vertical) ? new Thickness(0, stopWindowCoord, 0, 0) : new Thickness(stopWindowCoord, 0, 0, 0);
stopWindow.Width = lbi.ActualWidth;
stopWindow.Height = lbi.ActualHeight;
}
if (CanFilter)
{
if (filterEdit == null)
{
filterEdit = new TextBox();
filterEdit.HorizontalAlignment = HorizontalAlignment.Center;
filterEdit.VerticalAlignment = VerticalAlignment.Top;
filterEdit.Margin = new Thickness(3);
filterEdit.TextChanged += new TextChangedEventHandler(filterEdit_TextChanged);
filterEdit.PreviewKeyDown += new KeyEventHandler(filterEdit_KeyDown);
g.Children.Add(filterEdit);
}
filterEdit.Width = ActualWidth - 20;
}
}
catch (Exception) { }
}
//La apasarea tastei sageata jos in filtru, se coboara in lista
void filterEdit_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Down)
{
sp.Focusable = true;
Keyboard.Focus(sp);
}
}
//textul dupa care are loc filtrarea
void filterEdit_TextChanged(object sender, TextChangedEventArgs e)
{
Items.Filter = new Predicate<object>(PassesFilter);
}
public bool PassesFilter(Object value)
{
if (filterEdit.Text != "")
{
Regex regex = new Regex("^" + Regex.Escape(filterEdit.Text).Replace("\\*", ".*").Replace("\\?", ".") + ".*$");
return regex.IsMatch(extractText(value as UIElement));
}
else return true;
}
//tratarea evenimentului de selectie asupra unui element din lista
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
//MessageBox.Show(selectedIndex+" "+SelectedIndex);
if (!external)
{
//MessageBox.Show(SelectedIndex + "");
if (!selectionAllowed)
{
if (SelectedIndex != -1)
{
selectedIndex = SelectedIndex;
SelectedIndex = -1;
}
}
}
else selectionAllowed = false;
external = true;
base.OnSelectionChanged(e);
}
//Extrage textul care apare in interiorul oricarui control
private string extractText(UIElement item)
{
if (item is TextBlock) return ((TextBlock)item).Text;
if (item is TextBox) return ((TextBox)item).Text;
if (item is ContentControl)
{
object content = ((ContentControl)item).Content;
if (content is UIElement) return extractText(content as UIElement);
else return content.ToString();
}
else
{
string result = "";
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(item); i++)
result += extractText(VisualTreeHelper.GetChild(item, i) as UIElement) + " ";
return result;
}
}
//scroll prin rotita mouse-ului
void l_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
sp.Focusable = true;
Keyboard.Focus(sp);
adjustLimit = 0;
scrollLessThanZero = false;
scrollMoreThanEnd = false;
SelectedIndex = -1;
backOffset = ((Orientation == Orientation.Vertical) ? (sv.ExtentHeight - sv.ViewportHeight) : (sv.ExtentWidth - sv.ViewportWidth));
scroll_mouseDown = false;
scroll_v0 = -e.Delta / 100;
scroll_t0 = Environment.TickCount;
scroll_direction = (scroll_v0 >= 0);
scroll_offset0 = ((Orientation == Orientation.Vertical) ? sv.VerticalOffset - sp.Margin.Top : sv.HorizontalOffset - sp.Margin.Left);
scroll_timerClock.IsEnabled = true;
}
//la parasirea controlului de catre cursorul mouse-ului, cu butonul apasat, se simuleaza
//invocarea unui eveniment de eliberare a butonului mouse-ului
private void l_MouseLeave(object sender, MouseEventArgs e)
{
if (scroll_mouseDown)
{
MouseButtonEventArgs e1 = new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, MouseButton.Left);
e1.RoutedEvent = MouseLeaveEvent;
l_MouseUp(sender, e1);
}
}
//scroll controlat de miscarea mouse-ului
private void l_MouseMove(object sender, MouseEventArgs e)
{
if (scroll_mouseDown)
{
scroll_offset = scroll_init - ((Orientation == Orientation.Vertical) ? e.GetPosition(this).Y : e.GetPosition(this).X);
if (scroll_offset < 0)
if (ScrollLimitBehaviour == ScrollLimitBehaviour.Springs) sp.Margin = (Orientation == Orientation.Vertical) ? new Thickness(0, -scroll_offset, 0, 0) : new Thickness(-scroll_offset, 0, 0, 0);
else
{
if (canUpdate)
{
object elt = Items[Items.Count - 1];
Items.RemoveAt(Items.Count - 1);
Items.Insert(0, elt);
lbi = (ListBoxItem)ItemContainerGenerator.ContainerFromIndex(Items.Count - 1);
double adjust = (Orientation == Orientation.Vertical) ? lbi.ActualHeight : lbi.ActualWidth;
scroll_init += adjust;
canUpdate = false;
}
}
else
if (scroll_offset - backOffset > 0)
if (ScrollLimitBehaviour == ScrollLimitBehaviour.Springs) sp.Margin = (Orientation == Orientation.Vertical) ? new Thickness(0, 0, 0, scroll_offset - backOffset) : new Thickness(0, 0, scroll_offset - backOffset, 0);
else
{
if (canUpdate)
{
object elt = Items[0];
Items.RemoveAt(0);
Items.Add(elt);
lbi = (ListBoxItem)ItemContainerGenerator.ContainerFromIndex(0);
double adjust = (Orientation == Orientation.Vertical) ? lbi.ActualHeight : lbi.ActualWidth;
scroll_init -= adjust;
canUpdate = false;
}
}
else canUpdate = true;
if ((Orientation == Orientation.Vertical)) sv.ScrollToVerticalOffset(scroll_offset);
else sv.ScrollToHorizontalOffset(scroll_offset);
}
}
//comportamentul la eliberarea butonului mouse-ului; daca miscarea efectuata este una
//minora, se recurge la selectarea elementului vizat, altfel se continua cu initierea
//miscarii inertiale induse de acceleratia impusa de miscarea pana in acest moment
private void l_MouseUp(object sender, MouseButtonEventArgs e)
{
double move_offset = move_init - ((Orientation == Orientation.Vertical) ? e.GetPosition(this).Y : e.GetPosition(this).X);
selectionAllowed = (Math.Abs(move_offset) <= 10 && SelectedIndex != selectedIndex && StopWindowOffset < 0);
if (selectionAllowed) SelectedIndex = selectedIndex;
adjustLimit = 0;
if (scroll_mouseDown && move_offset != 0)
{
scroll_mouseDown = false;
scroll_span = Environment.TickCount - scroll_t0;
if (scroll_span > 0) scroll_v0 = move_offset / scroll_span;
else scroll_v0 = 0;
scroll_t0 = Environment.TickCount;
scroll_direction = (move_offset >= 0);
scroll_offset0 = ((Orientation == Orientation.Vertical) ? sv.VerticalOffset - sp.Margin.Top : sv.HorizontalOffset - sp.Margin.Left);
scroll_timerClock.IsEnabled = true;
}
else
{
scroll_mouseDown = false;
if (move_offset == 0) scrollStopped();
}
}
//timer-ul responsabil cu actualizarea deplasamentului de derulare in urma miscarii
//uniform incetinite de dupa eliberarea butonului mouse-ului
private void scroll_timerClock_Tick(object sender, EventArgs e)
{
double scroll_a = (scroll_direction ? -1 : 1) * ((scroll_out) ? 0.005 : 0.003),
scroll_t = Environment.TickCount - scroll_t0,
scroll = 0.5 * scroll_a * scroll_t * scroll_t + scroll_v0 * scroll_t + scroll_offset0,
scroll_v = scroll_a * scroll_t + scroll_v0;
if (scroll > 0 && scroll < Math.Abs(backOffset))
if (scroll_out)
{
sp.Margin = new Thickness(0, 0, 0, 0);
scroll_out = false;
scrollStopped();
}
else
{
if (Orientation == Orientation.Vertical) sv.ScrollToVerticalOffset(scroll);
else sv.ScrollToHorizontalOffset(scroll);
if ((scroll_v <= 0) == scroll_direction) scrollStopped();
}
else
if (ScrollLimitBehaviour == ScrollLimitBehaviour.Springs)
{
if (scroll < 0) scrollLessThanZero = true;
else scrollMoreThanEnd = true;
if (scrollLessThanZero && scrollMoreThanEnd)
{
sp.Margin = new Thickness(0, 0, 0, 0);
scrollLessThanZero = false;
scrollMoreThanEnd = false;
scroll_out = false;
scrollStopped();
}
else
{
scroll_out = true;
if (scroll > 0) sp.Margin = (Orientation == Orientation.Vertical) ? new Thickness(0, 0, 0, scroll - backOffset) : new Thickness(0, 0, scroll - backOffset, 0);
else sp.Margin = (Orientation == Orientation.Vertical) ? new Thickness(0, -scroll, 0, 0) : new Thickness(-scroll, 0, 0, 0);
if (Orientation == Orientation.Vertical) sv.ScrollToVerticalOffset(scroll);
else sv.ScrollToHorizontalOffset(scroll);
}
}
else
{
scroll_out = false;
if (scroll <= 0)
{
object elt = Items[Items.Count - 1];
Items.RemoveAt(Items.Count - 1);
Items.Insert(0, elt);
lbi = (ListBoxItem)ItemContainerGenerator.ContainerFromIndex(Items.Count - 1);
double adjust = (Orientation == Orientation.Vertical) ? lbi.ActualHeight : lbi.A

Related

Can WPF Listview has an element with different height that is embed in layout?

I've a listview that is dynamically populated by code.
In some case I need to put a note that has different height respect other components.
My goal is to have Note and listview items wrap all around, like in Word with image and Wrap Text set to "Square"
I tried to use UniformGrid and WrapPanel.
I tried to understand how write my own WrapPanel, but I don't find a way to solve it.
I think that I can have an external component for Note and leave listview items that are under Note like "Hidden"
<ListView x:Name="PluginListView"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
SelectionMode="Single"
VerticalContentAlignment="Top"
BorderThickness="0"
Margin="0,10,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<!--<UniformGrid Columns="4" HorizontalAlignment="Stretch" Rows="10"/>-->
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListView>
ADDED
I created a custom UniformGrid, I added a property NoteRowToSkip that rapresent the rows to skip in the last column on the left. I override ArrangeOverride and added the the skip behavior that I want.
Debugging seems that values are correct, but has the same behavior of UniformGrid.
public class UniformNoteWrapGrid : Panel
{
private int m_Rows;
private int m_Columns;
public static readonly DependencyProperty NoteRowToSkipProperty = DependencyProperty.Register(nameof(NoteRowToSkip), typeof(int), typeof(UniformGrid), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsMeasure), ValidateNoteRowToSkip);
private static bool ValidateNoteRowToSkip(object o)
{
return (int)o >= 0;
}
public int Columns
{
get => (int)GetValue(ColumnsProperty);
set => SetValue(ColumnsProperty, value);
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(
"Columns",
typeof(int),
typeof(UniformGrid),
new FrameworkPropertyMetadata(
0,
FrameworkPropertyMetadataOptions.AffectsMeasure),
ValidateColumns);
private static bool ValidateColumns(object o)
{
return (int)o >= 0;
}
public int Rows
{
get => (int)GetValue(RowsProperty);
set => SetValue(RowsProperty, value);
}
public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register("Rows", typeof(int),
typeof(UniformGrid),
new FrameworkPropertyMetadata(
0,
FrameworkPropertyMetadataOptions.AffectsMeasure),
ValidateRows);
private static bool ValidateRows(object o)
{
return (int)o >= 0;
}
public int NoteRowToSkip
{
get => (int)GetValue(NoteRowToSkipProperty);
set => SetValue(NoteRowToSkipProperty, value);
}
protected override Size MeasureOverride(Size constraint)
{
UpdateComputedValues();
var childConstraint = new Size(constraint.Width / m_Columns, constraint.Height / m_Rows);
double maxChildDesiredWidth = 0.0;
double maxChildDesiredHeight = 0.0;
for (int i = 0, count = InternalChildren.Count; i < count; ++i)
{
var child = InternalChildren[i];
child.Measure(childConstraint);
var childDesiredSize = child.DesiredSize;
if (maxChildDesiredWidth < childDesiredSize.Width)
maxChildDesiredWidth = childDesiredSize.Width;
if (maxChildDesiredHeight < childDesiredSize.Height)
maxChildDesiredHeight = childDesiredSize.Height;
}
return new Size(maxChildDesiredWidth * m_Columns, maxChildDesiredHeight * m_Rows);
}
protected override Size ArrangeOverride(Size arrangeSize)
{
var childBounds = new Rect(0, 0, arrangeSize.Width / m_Columns, arrangeSize.Height / m_Rows);
double xStep = childBounds.Width;
double xBound = arrangeSize.Width - 1.0;
var row = 1;
var column = 1;
foreach (UIElement child in InternalChildren)
{
child.Arrange(childBounds);
if (child.Visibility == Visibility.Collapsed)
continue;
childBounds.X += xStep;
column++;
var testXBound = xBound;
if (IsCellForNote(row, column))
testXBound -= childBounds.Height;
if (!(childBounds.X >= testXBound))
continue;
childBounds.Y += childBounds.Height;
childBounds.X = 0;
row++;
column = 1;
}
return arrangeSize;
}
private bool IsCellForNote(int row, int column)
{
if (row > NoteRowToSkip)
return true;
return column != Columns;
}
private void UpdateComputedValues()
{
m_Columns = Columns;
m_Rows = Rows;
//if (FirstColumn >= m_Columns)
// FirstColumn = 0;
if (m_Rows != 0 && m_Columns != 0)
return;
int nonCollapsedCount = 0;
for (int i = 0, count = InternalChildren.Count; i < count; ++i)
{
var child = InternalChildren[i];
if (child.Visibility != Visibility.Collapsed)
nonCollapsedCount++;
}
if (nonCollapsedCount == 0)
nonCollapsedCount = 1;
if (m_Rows == 0)
{
if (m_Columns > 0)
m_Rows = (nonCollapsedCount + (m_Columns - 1)) / m_Columns;
else
{
m_Rows = (int)Math.Sqrt(nonCollapsedCount);
if ((m_Rows * m_Rows) < nonCollapsedCount)
m_Rows++;
m_Columns = m_Rows;
}
}
else if (m_Columns == 0)
m_Columns = (nonCollapsedCount + (m_Rows - 1)) / m_Rows;
}
}
Any idea in what's not working in my code?
SOLVED
Was a problem with if conditions.
The working version is below.
public class UniformNoteWrapGrid : Panel
{
private int m_Rows;
private int m_Columns;
public static readonly DependencyProperty NoteRowToSkipProperty = DependencyProperty.Register(nameof(NoteRowToSkip), typeof(int), typeof(UniformGrid), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.AffectsMeasure), ValidateNoteRowToSkip);
private static bool ValidateNoteRowToSkip(object o)
{
return (int)o >= 0;
}
public int Columns
{
get => (int)GetValue(ColumnsProperty);
set => SetValue(ColumnsProperty, value);
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(
"Columns",
typeof(int),
typeof(UniformGrid),
new FrameworkPropertyMetadata(
0,
FrameworkPropertyMetadataOptions.AffectsMeasure),
ValidateColumns);
private static bool ValidateColumns(object o)
{
return (int)o >= 0;
}
public int Rows
{
get => (int)GetValue(RowsProperty);
set => SetValue(RowsProperty, value);
}
public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register("Rows", typeof(int),
typeof(UniformGrid),
new FrameworkPropertyMetadata(
0,
FrameworkPropertyMetadataOptions.AffectsMeasure),
ValidateRows);
private static bool ValidateRows(object o)
{
return (int)o >= 0;
}
public int NoteRowToSkip
{
get => (int)GetValue(NoteRowToSkipProperty);
set => SetValue(NoteRowToSkipProperty, value);
}
protected override Size MeasureOverride(Size constraint)
{
UpdateComputedValues();
var childConstraint = new Size(constraint.Width / m_Columns, constraint.Height / m_Rows);
double maxChildDesiredWidth = 0.0;
double maxChildDesiredHeight = 0.0;
for (int i = 0, count = InternalChildren.Count; i < count; ++i)
{
var child = InternalChildren[i];
child.Measure(childConstraint);
var childDesiredSize = child.DesiredSize;
if (maxChildDesiredWidth < childDesiredSize.Width)
maxChildDesiredWidth = childDesiredSize.Width;
if (maxChildDesiredHeight < childDesiredSize.Height)
maxChildDesiredHeight = childDesiredSize.Height;
}
return new Size(maxChildDesiredWidth * m_Columns, maxChildDesiredHeight * m_Rows);
}
protected override Size ArrangeOverride(Size arrangeSize)
{
var childBounds = new Rect(0, 0, arrangeSize.Width / m_Columns, arrangeSize.Height / m_Rows);
double xStep = childBounds.Width;
double xBound = arrangeSize.Width - 1.0;
var row = 1;
var column = 1;
foreach (UIElement child in InternalChildren)
{
child.Arrange(childBounds);
if (child.Visibility == Visibility.Collapsed)
continue;
childBounds.X += xStep;
column++;
var testXBound = xBound;
if (IsCellForNote(row, column))
testXBound -= xStep;
if (!(childBounds.X >= testXBound))
continue;
childBounds.Y += childBounds.Height;
childBounds.X = 0;
row++;
column = 1;
}
return arrangeSize;
}
private bool IsCellForNote(int row, int column)
{
if (row > NoteRowToSkip)
return false;
return column == Columns;
}
private void UpdateComputedValues()
{
m_Columns = Columns;
m_Rows = Rows;
//if (FirstColumn >= m_Columns)
// FirstColumn = 0;
if (m_Rows != 0 && m_Columns != 0)
return;
int nonCollapsedCount = 0;
for (int i = 0, count = InternalChildren.Count; i < count; ++i)
{
var child = InternalChildren[i];
if (child.Visibility != Visibility.Collapsed)
nonCollapsedCount++;
}
if (nonCollapsedCount == 0)
nonCollapsedCount = 1;
if (m_Rows == 0)
{
if (m_Columns > 0)
m_Rows = (nonCollapsedCount + (m_Columns - 1)) / m_Columns;
else
{
m_Rows = (int)Math.Sqrt(nonCollapsedCount);
if ((m_Rows * m_Rows) < nonCollapsedCount)
m_Rows++;
m_Columns = m_Rows;
}
}
else if (m_Columns == 0)
m_Columns = (nonCollapsedCount + (m_Rows - 1)) / m_Rows;
}
}

WPF - Is it possible to have a curved input box?

I want a textbox where you can input text from keyboard but be shown as an arc. Is it possible ?
I have found this solution in codeproject. Author created TextOnAPath control which can display curved text.
Source code:
[ContentProperty("Text")]
public class TextOnAPath : Control
{
static TextOnAPath()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TextOnAPath), new FrameworkPropertyMetadata(typeof(TextOnAPath)));
Control.FontSizeProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
Control.FontFamilyProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
Control.FontStretchProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
Control.FontStyleProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
Control.FontWeightProperty.OverrideMetadata(typeof(TextOnAPath),
new FrameworkPropertyMetadata(
new PropertyChangedCallback(OnFontPropertyChanged)));
}
static void OnFontPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == null || e.NewValue == e.OldValue)
return;
textOnAPath.UpdateText();
textOnAPath.Update();
}
double[] _segmentLengths;
TextBlock[] _textBlocks;
Panel _layoutPanel;
bool _layoutHasValidSize = false;
#region Text DP
public String Text
{
get { return (String)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(String), typeof(TextOnAPath),
new PropertyMetadata(null, new PropertyChangedCallback(OnStringPropertyChanged),
new CoerceValueCallback(CoerceTextValue)));
static void OnStringPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue || e.NewValue == null)
{
if (textOnAPath._layoutPanel != null)
textOnAPath._layoutPanel.Children.Clear();
return;
}
textOnAPath.UpdateText();
textOnAPath.Update();
}
static object CoerceTextValue(DependencyObject d, object baseValue)
{
if ((String)baseValue == "")
return null;
return baseValue;
}
#endregion
#region TextPath DP
public Geometry TextPath
{
get { return (Geometry)GetValue(TextPathProperty); }
set { SetValue(TextPathProperty, value); }
}
public static readonly DependencyProperty TextPathProperty =
DependencyProperty.Register("TextPath", typeof(Geometry), typeof(TextOnAPath),
new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(OnTextPathPropertyChanged)));
static void OnTextPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue || e.NewValue == null)
return;
textOnAPath.TextPath.Transform = null;
textOnAPath.UpdateSize();
textOnAPath.Update();
}
#endregion
#region DrawPath DP
/// <summary>
/// Set this property to True to display the TextPath geometry in the control
/// </summary>
public bool DrawPath
{
get { return (bool)GetValue(DrawPathProperty); }
set { SetValue(DrawPathProperty, value); }
}
public static readonly DependencyProperty DrawPathProperty =
DependencyProperty.Register("DrawPath", typeof(bool), typeof(TextOnAPath),
new PropertyMetadata(false, new PropertyChangedCallback(OnDrawPathPropertyChanged)));
static void OnDrawPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue || e.NewValue == null)
return;
textOnAPath.Update();
}
#endregion
#region DrawLinePath DP
/// <summary>
/// Set this property to True to display the line segments under the text (flattened path)
/// </summary>
public bool DrawLinePath
{
get { return (bool)GetValue(DrawLinePathProperty); }
set { SetValue(DrawLinePathProperty, value); }
}
// Using a DependencyProperty as the backing store for DrawFlattendPath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DrawLinePathProperty =
DependencyProperty.Register("DrawLinePath", typeof(bool), typeof(TextOnAPath),
new PropertyMetadata(false, new PropertyChangedCallback(OnDrawLinePathPropertyChanged)));
static void OnDrawLinePathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue || e.NewValue == null)
return;
textOnAPath.Update();
}
#endregion
#region ScaleTextPath DP
/// <summary>
/// If set to True (default) then the geometry defined by TextPath automatically gets scaled to fit the width/height of the control
/// </summary>
public bool ScaleTextPath
{
get { return (bool)GetValue(ScaleTextPathProperty); }
set { SetValue(ScaleTextPathProperty, value); }
}
public static readonly DependencyProperty ScaleTextPathProperty =
DependencyProperty.Register("ScaleTextPath", typeof(bool), typeof(TextOnAPath),
new PropertyMetadata(false, new PropertyChangedCallback(OnScaleTextPathPropertyChanged)));
static void OnScaleTextPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextOnAPath textOnAPath = d as TextOnAPath;
if (textOnAPath == null)
return;
if (e.NewValue == e.OldValue)
return;
bool value = (Boolean)e.NewValue;
if (value == false && textOnAPath.TextPath != null)
textOnAPath.TextPath.Transform = null;
textOnAPath.UpdateSize();
textOnAPath.Update();
}
#endregion
void UpdateText()
{
if (Text == null || FontFamily == null || FontWeight == null || FontStyle == null)
return;
_textBlocks = new TextBlock[Text.Length];
_segmentLengths = new double[Text.Length];
for (int i = 0; i < Text.Length; i++)
{
TextBlock t = new TextBlock();
t.FontSize = this.FontSize;
t.FontFamily = this.FontFamily;
t.FontStretch = this.FontStretch;
t.FontWeight = this.FontWeight;
t.FontStyle = this.FontStyle;
t.Text = new String(Text[i], 1);
t.RenderTransformOrigin = new Point(0.0, 1.0);
t.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
_textBlocks[i] = t;
_segmentLengths[i] = t.DesiredSize.Width;
}
}
void Update()
{
if (Text == null || TextPath == null || _layoutPanel == null || !_layoutHasValidSize)
return;
List<Point> intersectionPoints;
intersectionPoints = GeometryHelper.GetIntersectionPoints(TextPath.GetFlattenedPathGeometry(), _segmentLengths);
_layoutPanel.Children.Clear();
_layoutPanel.Margin = new Thickness(FontSize);
for (int i = 0; i < intersectionPoints.Count - 1; i++)
{
double oppositeLen = Math.Sqrt(Math.Pow(intersectionPoints[i].X + _segmentLengths[i] - intersectionPoints[i + 1].X, 2.0) + Math.Pow(intersectionPoints[i].Y - intersectionPoints[i + 1].Y, 2.0)) / 2.0;
double hypLen = Math.Sqrt(Math.Pow(intersectionPoints[i].X - intersectionPoints[i + 1].X, 2.0) + Math.Pow(intersectionPoints[i].Y - intersectionPoints[i + 1].Y, 2.0));
double ratio = oppositeLen / hypLen;
if (ratio > 1.0)
ratio = 1.0;
else if (ratio < -1.0)
ratio = -1.0;
//double angle = 0.0;
double angle = 2.0 * Math.Asin(ratio) * 180.0 / Math.PI;
// adjust sign on angle
if ((intersectionPoints[i].X + _segmentLengths[i]) > intersectionPoints[i].X)
{
if (intersectionPoints[i + 1].Y < intersectionPoints[i].Y)
angle = -angle;
}
else
{
if (intersectionPoints[i + 1].Y > intersectionPoints[i].Y)
angle = -angle;
}
TextBlock currTextBlock = _textBlocks[i];
RotateTransform rotate = new RotateTransform(angle);
TranslateTransform translate = new TranslateTransform(intersectionPoints[i].X, intersectionPoints[i].Y - currTextBlock.DesiredSize.Height);
TransformGroup transformGrp = new TransformGroup();
transformGrp.Children.Add(rotate);
transformGrp.Children.Add(translate);
currTextBlock.RenderTransform = transformGrp;
_layoutPanel.Children.Add(currTextBlock);
if (DrawLinePath == true)
{
Line line = new Line();
line.X1 = intersectionPoints[i].X;
line.Y1 = intersectionPoints[i].Y;
line.X2 = intersectionPoints[i + 1].X;
line.Y2 = intersectionPoints[i + 1].Y;
line.Stroke = Brushes.Black;
_layoutPanel.Children.Add(line);
}
}
// don't draw path if already drawing line path
if (DrawPath == true && DrawLinePath == false)
{
Path path = new Path();
path.Data = TextPath;
path.Stroke = Brushes.Black;
_layoutPanel.Children.Add(path);
}
}
public TextOnAPath()
{
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_layoutPanel = GetTemplateChild("LayoutPanel") as Panel;
if (_layoutPanel == null)
throw new Exception("Could not find template part: LayoutPanel");
_layoutPanel.SizeChanged += new SizeChangedEventHandler(_layoutPanel_SizeChanged);
}
Size _newSize;
void _layoutPanel_SizeChanged(object sender, SizeChangedEventArgs e)
{
_newSize = e.NewSize;
UpdateSize();
Update();
}
void UpdateSize()
{
if (_newSize == null || TextPath == null)
return;
_layoutHasValidSize = true;
double xScale = _newSize.Width / TextPath.Bounds.Width;
double yScale = _newSize.Height / TextPath.Bounds.Height;
if (TextPath.Bounds.Width <= 0)
xScale = 1.0;
if (TextPath.Bounds.Height <= 0)
xScale = 1.0;
if (xScale <= 0 || yScale <= 0)
return;
if (TextPath.Transform is TransformGroup)
{
TransformGroup grp = TextPath.Transform as TransformGroup;
if (grp.Children[0] is ScaleTransform && grp.Children[1] is TranslateTransform)
{
if (ScaleTextPath)
{
ScaleTransform scale = grp.Children[0] as ScaleTransform;
scale.ScaleX *= xScale;
scale.ScaleY *= yScale;
}
TranslateTransform translate = grp.Children[1] as TranslateTransform;
translate.X += -TextPath.Bounds.X;
translate.Y += -TextPath.Bounds.Y;
}
}
else
{
ScaleTransform scale;
TranslateTransform translate;
if (ScaleTextPath)
{
scale = new ScaleTransform(xScale, yScale);
translate = new TranslateTransform(-TextPath.Bounds.X * xScale, -TextPath.Bounds.Y * yScale);
}
else
{
scale = new ScaleTransform(1.0, 1.0);
translate = new TranslateTransform(-TextPath.Bounds.X, -TextPath.Bounds.Y );
}
TransformGroup grp = new TransformGroup();
grp.Children.Add(scale);
grp.Children.Add(translate);
TextPath.Transform = grp;
}
}
}
public static class GeometryHelper
{
public static List<Point> GetIntersectionPoints(PathGeometry FlattenedPath, double[] SegmentLengths)
{
List<Point> intersectionPoints = new List<Point>();
List<Point> pointsOnFlattenedPath = GetPointsOnFlattenedPath(FlattenedPath);
if (pointsOnFlattenedPath == null || pointsOnFlattenedPath.Count < 2)
return intersectionPoints;
Point currPoint = pointsOnFlattenedPath[0];
intersectionPoints.Add(currPoint);
// find point on flattened path that is segment length away from current point
int flattedPathIndex = 0;
int segmentIndex = 1;
while (flattedPathIndex < pointsOnFlattenedPath.Count - 1 &&
segmentIndex < SegmentLengths.Length + 1)
{
Point? intersectionPoint = GetIntersectionOfSegmentAndCircle(
pointsOnFlattenedPath[flattedPathIndex],
pointsOnFlattenedPath[flattedPathIndex + 1], currPoint, SegmentLengths[segmentIndex - 1]);
if (intersectionPoint == null)
flattedPathIndex++;
else
{
intersectionPoints.Add((Point)intersectionPoint);
currPoint = (Point)intersectionPoint;
pointsOnFlattenedPath[flattedPathIndex] = currPoint;
segmentIndex++;
}
}
return intersectionPoints;
}
static List<Point> GetPointsOnFlattenedPath(PathGeometry FlattenedPath)
{
List<Point> flattenedPathPoints = new List<Point>();
// for flattened geometry there should be just one PathFigure in the Figures
if (FlattenedPath.Figures.Count != 1)
return null;
PathFigure pathFigure = FlattenedPath.Figures[0];
flattenedPathPoints.Add(pathFigure.StartPoint);
// SegmentsCollection should contain PolyLineSegment and LineSegment
foreach (PathSegment pathSegment in pathFigure.Segments)
{
if (pathSegment is PolyLineSegment)
{
PolyLineSegment seg = pathSegment as PolyLineSegment;
foreach (Point point in seg.Points)
flattenedPathPoints.Add(point);
}
else if (pathSegment is LineSegment)
{
LineSegment seg = pathSegment as LineSegment;
flattenedPathPoints.Add(seg.Point);
}
else
throw new Exception("GetIntersectionPoint - unexpected path segment type: " + pathSegment.ToString());
}
return (flattenedPathPoints);
}
static Point? GetIntersectionOfSegmentAndCircle(Point SegmentPoint1, Point SegmentPoint2,
Point CircleCenter, double CircleRadius)
{
// linear equation for segment: y = mx + b
double slope = (SegmentPoint2.Y - SegmentPoint1.Y) / (SegmentPoint2.X - SegmentPoint1.X);
double intercept = SegmentPoint1.Y - (slope * SegmentPoint1.X);
// special case when segment is vertically oriented
if (double.IsInfinity(slope))
{
double root = Math.Pow(CircleRadius, 2.0) - Math.Pow(SegmentPoint1.X - CircleCenter.X, 2.0);
if (root < 0)
return null;
// soln 1
double SolnX1 = SegmentPoint1.X;
double SolnY1 = CircleCenter.Y - Math.Sqrt(root);
Point Soln1 = new Point(SolnX1, SolnY1);
// have valid result if point is between two segment points
if (IsBetween(SolnX1, SegmentPoint1.X, SegmentPoint2.X) &&
IsBetween(SolnY1, SegmentPoint1.Y, SegmentPoint2.Y))
//if (ValidSoln(Soln1, SegmentPoint1, SegmentPoint2, CircleCenter))
{
// found solution
return (Soln1);
}
// soln 2
double SolnX2 = SegmentPoint1.X;
double SolnY2 = CircleCenter.Y + Math.Sqrt(root);
Point Soln2 = new Point(SolnX2, SolnY2);
// have valid result if point is between two segment points
if (IsBetween(SolnX2, SegmentPoint1.X, SegmentPoint2.X) &&
IsBetween(SolnY2, SegmentPoint1.Y, SegmentPoint2.Y))
//if (ValidSoln(Soln2, SegmentPoint1, SegmentPoint2, CircleCenter))
{
// found solution
return (Soln2);
}
}
else
{
// use soln to quadradratic equation to solve intersection of segment and circle:
// x = (-b +/ sqrt(b^2-4ac))/(2a)
double a = 1 + Math.Pow(slope, 2.0);
double b = (-2 * CircleCenter.X) + (2 * (intercept - CircleCenter.Y) * slope);
double c = Math.Pow(CircleCenter.X, 2.0) + Math.Pow(intercept - CircleCenter.Y, 2.0) - Math.Pow(CircleRadius, 2.0);
// check for no solutions, is sqrt negative?
double root = Math.Pow(b, 2.0) - (4 * a * c);
if (root < 0)
return null;
// we might have two solns...
// soln 1
double SolnX1 = (-b + Math.Sqrt(root)) / (2 * a);
double SolnY1 = slope * SolnX1 + intercept;
Point Soln1 = new Point(SolnX1, SolnY1);
// have valid result if point is between two segment points
if (IsBetween(SolnX1, SegmentPoint1.X, SegmentPoint2.X) &&
IsBetween(SolnY1, SegmentPoint1.Y, SegmentPoint2.Y))
//if (ValidSoln(Soln1, SegmentPoint1, SegmentPoint2, CircleCenter))
{
// found solution
return (Soln1);
}
// soln 2
double SolnX2 = (-b - Math.Sqrt(root)) / (2 * a);
double SolnY2 = slope * SolnX2 + intercept;
Point Soln2 = new Point(SolnX2, SolnY2);
// have valid result if point is between two segment points
if (IsBetween(SolnX2, SegmentPoint1.X, SegmentPoint2.X) &&
IsBetween(SolnY2, SegmentPoint1.Y, SegmentPoint2.Y))
//if (ValidSoln(Soln2, SegmentPoint1, SegmentPoint2, CircleCenter))
{
// found solution
return (Soln2);
}
}
// shouldn't get here...but in case
return null;
}
static bool IsBetween(double X, double X1, double X2)
{
if (X1 >= X2 && X <= X1 && X >= X2)
return true;
if (X1 <= X2 && X >= X1 && X <= X2)
return true;
return false;
}
}
Usage:
<TextOnAPath:TextOnAPath FontSize="30" DrawPath="True"
Text="The quick brown fox jumped over the lazy hen.">
<TextOnAPath:TextOnAPath.TextPath>
<PathGeometry Figures="M0,0 C120,361 230.5,276.5 230.5,276.5
L308.5,237.50001 C308.5,237.50001 419.5,179.5002 367.5,265.49993
315.5,351.49966 238.50028,399.49924 238.50028,399.49924 L61.500017,
420.49911"/>
</TextOnAPath:TextOnAPath.TextPath>
</TextOnAPath:TextOnAPath>
</Grid>

DataGridComboboxColumn - Get cell value

The task is the following: there is a DataGrid with ComboboxColumns. If user changes cell[2,3] and selected value!=0 then disable cell[3,2]. I wrote the following Handler:
private void grAssessment_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
int x = e.Column.DisplayIndex;
int y = e.Row.GetIndex();
grAssessment.GetCell(x, y).IsEnabled = false;
grAssessment.GetCell(x, y).Background = Brushes.LightGray;
}
But it disables appropriate cell in anyway. How to add selected value!=0 condition to this code?
Thanks in advance.
Try to check this out:
1. DataGridComboBoxColumn ItemsSource (for current version only, use your own):
private ObservableCollection<ScoreData> _scores = new ObservableCollection<ScoreData>(
new List<ScoreData>
{
new ScoreData{Score = 1, ScoreVerbal = "the same"},
new ScoreData{Score = 3, ScoreVerbal = "moderate superiority"},
new ScoreData{Score = 5, ScoreVerbal = "strong superiority"},
new ScoreData{Score = 7, ScoreVerbal = "the samvery strong superioritye"},
new ScoreData{Score = 9, ScoreVerbal = "extremely superiority"},
} );
2. Handler code (the handler defined on the data grid):
private void SelectDataGrid_OnCellEditEnding(object sender,
DataGridCellEditEndingEventArgs e)
{
var editingElement = e.EditingElement as Selector;
if(editingElement == null) return;
var selectedData = editingElement.SelectedItem as ScoreData;
if(selectedData == null || selectedData.Score > 1) return;
var dataGridCell = editingElement.GetVisualParentOfType<DataGridCell>();
if(dataGridCell == null) return;
dataGridCell.IsEnabled = false;
}
3. GetVisualParentOfType code (as part of extension class):
public static class VisualTreeExtensions
{
public static T GetVisualParentOfType<T>(this DependencyObject child)
where T : DependencyObject
{
if (child is T)
return child as T;
var parentObject = VisualTreeHelper.GetParent(child);
if (parentObject == null) return null;
var parent = parentObject as T;
return parent ?? GetVisualParentOfType<T>(parentObject);
}
public static T GetChildOfType<T>(this DependencyObject depObj)
where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
}
regards,
I found the following solution
private void grAssessment_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
int x = e.Column.DisplayIndex;
int y = e.Row.GetIndex();
DataGridCell cell = grAssessment.GetCell(y, x);
if (((System.Windows.Controls.ComboBox)(cell.Content)).Text != "")
{
grAssessment.GetCell(x, y).IsEnabled = false;
grAssessment.GetCell(x, y).Background = Brushes.LightGray;
}
else
{
grAssessment.GetCell(x, y).IsEnabled = true;
grAssessment.GetCell(x, y).Background = Brushes.White;
}
}

WPF ICollectionView Filtering

I've written a code for filtering items in ComboBox:
My question is, how would you do that?
I think that this solution with reflection could be very slow..
ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
view.Filter += this.FilterPredicate;
private bool FilterPredicate(object value)
{
if (value == null)
return false;
if (String.IsNullOrEmpty(SearchedText))
return true;
int index = value.ToString().IndexOf(
SearchedText,
0,
StringComparison.InvariantCultureIgnoreCase);
if ( index > -1) return true;
return FindInProperties(new string[] { "Property1", "Property2" }, value, SearchedText);
}
private bool FindInProperties(string[] properties, object value, string txtToFind)
{
PropertyInfo info = null;
for (int i = 0; i < properties.Length; i++)
{
info = value.GetType().GetProperty(properties[i]);
if (info == null) continue;
object s = info.GetValue(value, null);
if (s == null) continue;
int index = s.ToString().IndexOf(
txtToFind,
0,
StringComparison.InvariantCultureIgnoreCase);
if (index > -1) return true;
}
return false;
}
Why not just this:
ICollectionView view = CollectionViewSource.GetDefaultView(newValue);
IEqualityComparer<String> comparer = StringComparer.InvariantCultureIgnoreCase;
view.Filter = o => {
Person p = o as Person;
return p.FirstName.Contains(SearchedText, comparer)
|| p.LastName.Contains(SearchedText, comparer);
}
Do you need to search properties dynamically?

Control for tags with auto-completion in Winforms?

I am seeking a WinForm control that would provide an autocomplete behavior for multiple space-separated - exactly ala del.icio.us (or stackoverflow.com for that matter).
Does anyone knows how to do that within a .NET 2.0 WinForm application?
ComboBox can autocomplete, but only one word at a time.
If you want to have each word separately autocompleted, you have to write your own.
I already did, hope it's not too long. It's not 100% exactly what you want, this was used for autocompleting in email client when typing in email adress.
/// <summary>
/// Extended TextBox with smart auto-completion
/// </summary>
public class TextBoxAC: TextBox
{
private List<string> completions = new List<string>();
private List<string> completionsLow = new List<string>();
private bool autocompleting = false;
private bool acDisabled = true;
private List<string> possibleCompletions = new List<string>();
private int currentCompletion = 0;
/// <summary>
/// Default constructor
/// </summary>
public TextBoxAC()
{
this.TextChanged += new EventHandler(TextBoxAC_TextChanged);
this.KeyPress += new KeyPressEventHandler(TextBoxAC_KeyPress);
this.KeyDown += new KeyEventHandler(TextBoxAC_KeyDown);
this.TabStop = true;
}
/// <summary>
/// Sets autocompletion data, list of possible strings
/// </summary>
/// <param name="words">Completion words</param>
/// <param name="wordsLow">Completion words in lowerCase</param>
public void SetAutoCompletion(List<string> words, List<string> wordsLow)
{
if (words == null || words.Count < 1) { return; }
this.completions = words;
this.completionsLow = wordsLow;
this.TabStop = false;
}
private void TextBoxAC_TextChanged(object sender, EventArgs e)
{
if (this.autocompleting || this.acDisabled) { return; }
string text = this.Text;
if (text.Length != this.SelectionStart) { return; }
int pos = this.SelectionStart;
string userPrefix = text.Substring(0, pos);
int commaPos = userPrefix.LastIndexOf(",");
if (commaPos == -1)
{
userPrefix = userPrefix.ToLower();
this.possibleCompletions.Clear();
int n = 0;
foreach (string s in this.completionsLow)
{
if (s.StartsWith(userPrefix))
{
this.possibleCompletions.Add(this.completions[n]);
}
n++;
}
if (this.possibleCompletions.Count < 1) { return; }
this.autocompleting = true;
this.Text = this.possibleCompletions[0];
this.autocompleting = false;
this.SelectionStart = pos;
this.SelectionLength = this.Text.Length - pos;
}
else
{
string curUs = userPrefix.Substring(commaPos + 1);
if (curUs.Trim().Length < 1) { return; }
string trimmed;
curUs = this.trimOut(curUs, out trimmed);
curUs = curUs.ToLower();
string oldUs = userPrefix.Substring(0, commaPos + 1);
this.possibleCompletions.Clear();
int n = 0;
foreach (string s in this.completionsLow)
{
if (s.StartsWith(curUs))
{
this.possibleCompletions.Add(this.completions[n]);
}
n++;
}
if (this.possibleCompletions.Count < 1) { return; }
this.autocompleting = true;
this.Text = oldUs + trimmed + this.possibleCompletions[0];
this.autocompleting = false;
this.SelectionStart = pos;
this.SelectionLength = this.Text.Length - pos + trimmed.Length;
}
this.currentCompletion = 0;
}
private void TextBoxAC_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Back || e.KeyCode == Keys.Delete)
{
this.acDisabled = true;
}
if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down)
{
if ((this.acDisabled) || (this.possibleCompletions.Count < 1))
{
return;
}
e.Handled = true;
if (this.possibleCompletions.Count < 2) { return; }
switch (e.KeyCode)
{
case Keys.Up:
this.currentCompletion--;
if (this.currentCompletion < 0)
{
this.currentCompletion = this.possibleCompletions.Count - 1;
}
break;
case Keys.Down:
this.currentCompletion++;
if (this.currentCompletion >= this.possibleCompletions.Count)
{
this.currentCompletion = 0;
}
break;
}
int pos = this.SelectionStart;
string userPrefix = this.Text.Substring(0, pos);
int commaPos = userPrefix.LastIndexOf(",");
if (commaPos == -1)
{
pos--;
userPrefix = this.Text.Substring(0, pos);
this.autocompleting = true;
this.Text = userPrefix + this.possibleCompletions[this.currentCompletion].Substring(userPrefix.Length);
this.autocompleting = false;
this.SelectionStart = pos + 1;
this.SelectionLength = this.Text.Length - pos;
}
else
{
string curUs = userPrefix.Substring(commaPos + 1);
if (curUs.Trim().Length < 1) { return; }
string trimmed;
curUs = this.trimOut(curUs, out trimmed);
curUs = curUs.ToLower();
string oldUs = userPrefix.Substring(0, commaPos + 1);
this.autocompleting = true;
this.Text = oldUs + trimmed + this.possibleCompletions[this.currentCompletion];
this.autocompleting = false;
this.SelectionStart = pos;
this.SelectionLength = this.Text.Length - pos + trimmed.Length;
}
}
}
private void TextBoxAC_KeyPress(object sender, KeyPressEventArgs e)
{
if (!Char.IsControl(e.KeyChar)) { this.acDisabled = false; }
}
private string trimOut(string toTrim, out string trim)
{
string ret = toTrim.TrimStart();
int pos = toTrim.IndexOf(ret);
trim = toTrim.Substring(0, pos);
return ret;
}
}

Resources