How to set the margin on a internal TextBoxView in wpf - c#

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.

Related

INotifyDataErrorInfo (sometimes) does not work

i wrote a control, derived from Textbox, where I can enter numbers in a special format.
To ensure, this format is correct I also implemented INotifyDataErrorInfo for validation.
However, after a few tests everything seems fine. The validaton pops up and also disappears again when the error has been fixed.
But now, I wanted to use the same control in another window and there it doesn't work anymore. The validation happens, the error is added to the dictionary and OnErrorsChanged is called, but after the ErrorHandler gets invoked neither the HasError property gets updated nor the GetErrors method is called and I cannot find out why this is the case.
In the other window, as said, everything works as expected.
Here is the important part of the control
public class UnitedStatesCustomaryUnitTextBox : TextBox, INotifyDataErrorInfo
{
static UnitedStatesCustomaryUnitTextBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(UnitedStatesCustomaryUnitTextBox), new FrameworkPropertyMetadata(typeof(UnitedStatesCustomaryUnitTextBox)));
}
private readonly Dictionary<string, List<string>> _propertyErrors = new Dictionary<string, List<string>>();
#region property Notifications
public static readonly DependencyProperty NotificationsProperty = DependencyProperty.Register(
"Notifications",
typeof(List<Notification>),
typeof(UnitedStatesCustomaryUnitTextBox),
new PropertyMetadata(default(List<Notification>), OnNotificationsChanged));
public List<Notification> Notifications
{
get
{
var result = (List<Notification>)GetValue(NotificationsProperty);
if (result != null)
return result;
result = new List<Notification>();
SetValue(NotificationsProperty, result);
return result;
}
set { SetValue(NotificationsProperty, value); }
}
private static void OnNotificationsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var ctl = sender as UnitedStatesCustomaryUnitTextBox;
if (ctl == null)
return;
var oldValue = e.OldValue as List<Notification>;
var newValue = e.NewValue as List<Notification>;
ctl.OnNotificationsChanged(oldValue, newValue);
}
private void OnNotificationsChanged(List<Notification> oldValue, List<Notification> newValue)
{
Client.Controls.UnitedStatesCustomaryUnitTextBox.UnitedStatesCustomaryUnitTextBox.OnNotificationsChanged
}
#endregion
#region property LengthUomId
public static readonly DependencyProperty LengthUomIdProperty = DependencyProperty.Register(
"LengthUomId",
typeof(int?),
typeof(UnitedStatesCustomaryUnitTextBox),
new PropertyMetadata(default(int?), OnLengthUomIdChanged));
public int? LengthUomId
{
get => (int?)GetValue(LengthUomIdProperty);
set => this.SetValue(LengthUomIdProperty, value);
}
#endregion
#region property Value
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value",
typeof(decimal?),
typeof(UnitedStatesCustomaryUnitTextBox),
new PropertyMetadata(default(decimal?), OnValueChanged));
public decimal? Value
{
get => (decimal?)GetValue(ValueProperty);
set => this.SetValue(ValueProperty, value);
}
private static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var ctl = sender as UnitedStatesCustomaryUnitTextBox;
if (ctl == null)
{
return;
}
var oldValue = e.OldValue as decimal?;
var newValue = e.NewValue as decimal?;
ctl.OnValueChanged(oldValue, newValue);
}
private void OnValueChanged(decimal? oldValue, decimal? newValue)
{
if (!this._isCalculating)
this.SetCurrentValue(TextProperty, this.CalculateFeetInchSixteenth(newValue));
}
#endregion
private bool IsFeetInchSixteenth => Defaults.UomDefaults.DefaultLengthUomId == this.LengthUomId;
protected override void OnTextChanged(TextChangedEventArgs e)
{
this._isCalculating = true;
if (!this.IsFeetInchSixteenth)
{
if (decimal.TryParse(this.Text, out decimal d))
this.Value = d;
return;
}
if (this.ValidateText(this.Text))
this.CalculateValue(this.Text);
this._isCalculating = false;
base.OnTextChanged(e);
}
private bool _isCalculating { get; set; }
private void CalculateValue(string text)
{
var numbers = text.Split('-');
this.Value = Convert.ToDecimal(
int.Parse(numbers[0]) * 192 +
(int.Parse(numbers[1]) * 16) +
(int.Parse(numbers[2]) * 1));
}
private string CalculateFeetInchSixteenth(decimal? value)
{
if (value == null)
return "0-0-0";
var feet = Math.Truncate(value.Value / 192);
var inch = Math.Truncate((value.Value - (feet * 192)) / 16);
var sixteenth = Math.Truncate(value.Value - (feet * 192) - (inch * 16));
return $"{feet}-{inch}-{sixteenth}";
}
private bool ValidateText(string text)
{
this._propertyErrors.Clear();
this.Notifications?.Clear();
this.OnErrorsChanged(nameof(this.Text));
if (string.IsNullOrWhiteSpace(text))
return false;
var numbers = text.Split('-');
if (numbers.Length != 3)
{
var notification = new Notification(
NotificationType.Error,
"FISC0001",
NotificationResources.FISC0001,
NotificationLocalizer.Localize(() => NotificationResources.FISC0001, new object[] { string.Empty }),
null);
this.AddError(nameof(this.Text), notification);
return false;
}
if (!this.CheckNumberRange(numbers))
{
var notification = new Notification(
NotificationType.Error,
"FISC0002",
NotificationResources.FISC0002,
NotificationLocalizer.Localize(() => NotificationResources.FISC0002, new object[] { string.Empty }),
null);
this.AddError(nameof(this.Text), notification);
return false;
}
return true;
}
private bool CheckNumberRange(string[] numbers)
{
if (!int.TryParse(numbers[0], out int number1))
return false;
if (!int.TryParse(numbers[1], out int number2))
return false;
if (!int.TryParse(numbers[2], out int number3))
return false;
return this.IsBetween(number2, 0, 11) && this.IsBetween(number3, 0, 15);
}
[DebuggerStepThrough]
private bool IsBetween(int number, int min, int max)
{
return number >= min && number <= max;
}
public IEnumerable GetErrors(string propertyName)
{
this._propertyErrors.TryGetValue(propertyName, out List<string> errors);
return errors;
}
public bool HasErrors => this._propertyErrors.Any();
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public void AddError(string propertyName, Notification notification)
{
if (!this._propertyErrors.ContainsKey(propertyName))
this._propertyErrors.Add(propertyName, new List<string>());
this._propertyErrors[propertyName].Add(notification.LocalizedMessage ?? notification.Message);
this.OnErrorsChanged(propertyName);
this.Notifications.Add(notification);
}
private void OnErrorsChanged(string propertyName)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
}
}
In the xaml I'm using the control like this
<unitedStatesCustomaryUnitTextBox:UnitedStatesCustomaryUnitTextBox
HorizontalAlignment="Stretch"
Visibility="{Binding IsLengthUnitedStatesCustomaryUnit.Value, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource FalseToCollapsedConverter}}"
Value="{Binding MeasuredLiquidLevelValue.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
LengthUomId="{Binding MeasuredLiquidLevelUom.Value.Id}"
Notifications="{Binding LengthErrors}"
Margin="{DynamicResource DefaultMarginAll}">
<i:Interaction.Behaviors>
<ncshared:LostFocusToCommandBehavior Command="{Binding CalculationRelatedControlLostFocusCommand}" />
</i:Interaction.Behaviors>
</unitedStatesCustomaryUnitTextBox:UnitedStatesCustomaryUnitTextBox>
I also tried setting the properties like ValidateOnDataErrors to true with no effect (they are true by default I think)
Thanks for the answers.
I implemented the changes you suggested regarding the DependencyProperty getters.
Also I found a solution for my problem.
Setting ValidateOnNotifyDataErrors resolved the problem with the GetErrors method not being called.
It also seems that I just forgot to set a style for the control (derived from Textbox), so the application didn't know how to render the validation error.
Although it shouldn't it works fine now

Specified element is already the logical child of another element. Disconnect it first. in User Control

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.

Zoom into ListView contents without also scaling scroll bars

I have a GridView in a ListView. I want to add a Ctrl+MWheelUp zoom to the contents.
I've achieved the zoom part using a ScaleTransform with the below code, however, as this is applied to the ListView as a whole, it also scales the scroll bars. Ideally, I'd like scrollbars to remain a fixed size (although obviously adjusting to the change in inner-content) - however, I'm not sure how I could achieve this. Would the only way to be to apply the ScaleTransform to every child of every GridViewColumn, or is there another method I could use to apply it to the ListView as a whole, without also scaling the scroll bars?
My (simplified) xaml:
<ListView ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
x:Name="listView">
<ListView.View>
<GridView>
<GridViewColumn>...</GridViewColumn>
<GridViewColumn>...</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
xaml.cs:
public Control()
{
InitializeComponent();
var mouseWheelZoom = new MouseWheelZoom(listView);
PreviewMouseWheel += mouseWheelZoom.Zoom;
}
MouseWheelZoom
public class MouseWheelZoom
{
private readonly FrameworkElement _element;
private double _currentZoomFactor;
public MouseWheelZoom(FrameworkElement element)
{
_element = element;
_currentZoomFactor = 1.0;
}
public void Zoom(object sender, MouseWheelEventArgs e)
{
var handle = (Keyboard.Modifiers & ModifierKeys.Control) > 0;
if (!handle)
return;
ApplyZoom(e.Delta);
}
private void ApplyZoom(int delta)
{
var zoomScale = delta / 500.0;
var newZoomFactor = _currentZoomFactor += zoomScale;
_element.LayoutTransform = new ScaleTransform(newZoomFactor, newZoomFactor);
_currentZoomFactor = newZoomFactor;
}
}
I just created an attached property that seems to produce the effect you are looking for. Here is the code for the property.
internal static class ListViewBehaviors
{
public static readonly DependencyProperty ContentTransformProperty = DependencyProperty.RegisterAttached("ContentTransform", typeof(Transform), typeof(ListViewBehaviors),
new PropertyMetadata((Transform)null, OnContentTransformChanged));
public static Transform GetContentTransform(ListView obj)
{
return (Transform)obj.GetValue(ContentTransformProperty);
}
public static void SetContentTransform(ListView obj, Transform value)
{
obj.SetValue(ContentTransformProperty, value);
}
private static void OnContentTransformChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
ListView view = obj as ListView;
if (view != null)
{
if (view.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated)
{
EventHandler handler = null;
handler = (s, a) => ListView_ItemContainerGenerator_StatusChanged(view, handler);
view.ItemContainerGenerator.StatusChanged += handler;
}
else
{
UpdateTransform(view);
}
}
}
private static void ListView_ItemContainerGenerator_StatusChanged(ListView view, EventHandler handler)
{
if (view.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
view.ItemContainerGenerator.StatusChanged -= handler;
UpdateTransform(view);
}
}
private static void UpdateTransform(ListView view)
{
if (view.IsArrangeValid)
{
DoUpdateTransform(view);
}
else
{
EventHandler handler = null;
handler = (s, e) => LayoutUpdated(view, handler);
view.LayoutUpdated += handler;
}
}
private static void LayoutUpdated(ListView view, EventHandler handler)
{
view.LayoutUpdated -= handler;
DoUpdateTransform(view);
}
private static void DoUpdateTransform(ListView view)
{
ScrollViewer scroller = VisualTreeUtility.FindDescendant<ScrollViewer>(view);
if (scroller != null)
{
Transform transform = GetContentTransform(view);
FrameworkElement header = VisualTreeUtility.FindDescendant<ScrollViewer>(scroller);
if (header != null)
{
header.LayoutTransform = transform;
}
FrameworkElement content = scroller.Template.FindName("PART_ScrollContentPresenter", scroller) as FrameworkElement;
if (content != null)
{
content.LayoutTransform = transform;
}
}
}
}
Also, here is the code for the VisualTreeUtility.FindDescendant method(s).
public static class VisualTreeUtility
{
public static T FindDescendant<T>(DependencyObject ancestor) where T : DependencyObject
{
return FindDescendant<T>(ancestor, item => true);
}
public static T FindDescendant<T>(DependencyObject ancestor, Predicate<T> predicate) where T : DependencyObject
{
return FindDescendant(typeof(T), ancestor, item => predicate((T)item)) as T;
}
public static DependencyObject FindDescendant(Type itemType, DependencyObject ancestor, Predicate<DependencyObject> predicate)
{
if (itemType == null) throw new ArgumentNullException("itemType");
if (ancestor == null) throw new ArgumentNullException("ancestor");
if (predicate == null) throw new ArgumentNullException("predicate");
if (!typeof(DependencyObject).IsAssignableFrom(itemType)) throw new ArgumentException("itemType", "The passed in type must be or extend DependencyObject");
Queue<DependencyObject> queue = new Queue<DependencyObject>();
queue.Enqueue(ancestor);
while (queue.Count > 0)
{
DependencyObject currentChild = queue.Dequeue();
if (currentChild != ancestor && itemType.IsAssignableFrom(currentChild.GetType()))
{
if(predicate.Invoke(currentChild))
{
return currentChild;
}
}
int count = VisualTreeHelper.GetChildrenCount(currentChild);
for (int i = 0; i < count; ++i)
{
queue.Enqueue(VisualTreeHelper.GetChild(currentChild, i));
}
}
return null;
}
}
And here is how you can use the property:
<ListView
ItemsSource="{Binding Items}">
<local:ListViewBehaviors.ContentTransform>
<ScaleTransform ScaleX="2" ScaleY="2" />
</local:ListViewBehaviors.ContentTransform>
<ListView.View>
...
</ListView.View>
</ListView>
Let me know if anything is missing or confusing.
You could use the always handy FindChild<T> function to retrieve the ScrollContentPresenter inside the ListView, and use your zooming function with it.
public Control()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(Control_Loaded);
}
private void Control_Loaded(object sender, RoutedEventArgs e)
{
var presenter = FindChild<ScrollContentPresenter>(listView, null);
var mouseWheelZoom = new MouseWheelZoom(presenter);
PreviewMouseWheel += mouseWheelZoom.Zoom;
}
Note that I've put the code inside the Loaded event handler. That's because the ScrollContentPresenter is part of the ListView Template and not a direct part of your view, so it won't exist until the control is fully loaded with its Styles and Templates.
PD.: Also worth noting that some other parts of the ListView, like headers and such, won't be zoomed. Only the items will.
Haven't tried, but you can try to do something like
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel /> <!-- scale this, it's inside ScrollViewer -->
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>

Highlighting keywords in a richtextbox in WPF [duplicate]

This question already has an answer here:
Highlight text in RichTextBox
(1 answer)
Closed 2 years ago.
I'm making a program which needs to look through a paragraph of text and find how many times a certain keyword/keywords appear. It also has to highlight each of these key words in the text.
I have managed to make he interface and it can now track how many times the word appears but I am really stuck for how to highlight where the keywords appear. I will post my code below, any help is greatly appreciated on how to search for and highlight text inside a richtextbox. Since this is in WPF the obvious richtextbox.find() is not avaliable for use.
class TextAnalyser
{
public int FindNumberOfOccurances(List<string> keywords, string email)
{
int occurances = 0;
foreach (string keyword in keywords)
{
occurances += email.ToUpper().Split(new string[] { keyword.ToUpper() }, StringSplitOptions.None).Count() - 1;
}
return occurances;
}
public void TurnTextRed(List<string> keywords, string email, RichTextBox TextBox)
{
foreach(string keyword in keywords)
{
}
}
public List<string> ConvertTextToList(string text)
{
char[] splitChars = {};
string[] ArrayText = text.Split( splitChars, StringSplitOptions.RemoveEmptyEntries);
return ArrayText.ToList<string>();
}
public string GetStringFromTextBox(RichTextBox TextBox)
{
var textRange = new TextRange(
TextBox.Document.ContentStart,
TextBox.Document.ContentEnd
);
return textRange.Text;
}
}
And here is my Main Window
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void AnalyseButton_Click(object sender, RoutedEventArgs e)
{
var textTool = new TextAnalyser();
var keyWords = textTool.ConvertTextToList(textTool.GetStringFromTextBox(WordTextBox).Trim());
var email = textTool.GetStringFromTextBox(EmailTextBox).Trim();
int usesOfWord = textTool.FindNumberOfOccurances(keyWords, email);
Occurances.Text = usesOfWord.ToString();
}
}
Here is the method is used to get all of word in richtextbox's document.
public static IEnumerable<TextRange> GetAllWordRanges(FlowDocument document)
{
string pattern = #"[^\W\d](\w|[-']{1,2}(?=\w))*";
TextPointer pointer = document.ContentStart;
while (pointer != null)
{
if (pointer.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
{
string textRun = pointer.GetTextInRun(LogicalDirection.Forward);
MatchCollection matches = Regex.Matches(textRun, pattern);
foreach (Match match in matches)
{
int startIndex = match.Index;
int length = match.Length;
TextPointer start = pointer.GetPositionAtOffset(startIndex);
TextPointer end = start.GetPositionAtOffset(length);
yield return new TextRange(start, end);
}
}
pointer = pointer.GetNextContextPosition(LogicalDirection.Forward);
}
}
You can change the pattern which is used to split words.
At last, easy to highlight your words.
IEnumerable<TextRange> wordRanges = GetAllWordRanges(RichTextBox.Document);
foreach (TextRange wordRange in wordRanges)
{
if (wordRange.Text == "keyword")
{
wordRange.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Red);
}
}
Ran across a need for this and couldn't find any suitable solutions. (Using a TextBox for binding, highlighting on the fly, multiple hits and colors, etc.) This can obviously be extended to suit your needs. This references a couple of extension methods that add/remove adorners of the specified Type T from the UIElement's adorner layer.
public class HighlightRule
{
public SolidColorBrush Brush { get; set; }
public string MatchText { get; set; }
public HighlightRule(SolidColorBrush solidColorBrush, string matchText)
{
Brush = solidColorBrush;
MatchText = matchText;
}
public HighlightRule(Color color, string matchText)
{
Brush = new SolidColorBrush(color);
MatchText = matchText;
}
public HighlightRule()
{
MatchText = null;
Brush = Brushes.Black;
}
}
public class HighlightTextBox : TextBox
{
public List<HighlightRule> HighlightRules
{
get { return ( List<HighlightRule>)GetValue(HighlightRulesProperty); }
set { SetValue(HighlightRulesProperty, value); }
}
// Using a DependencyProperty as the backing store for HighlightRules. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HighlightRulesProperty =
DependencyProperty.Register("HighlightRules", typeof(List<HighlightRule>), typeof(HighlightTextBox), new FrameworkPropertyMetadata(new List<HighlightRule>(), new PropertyChangedCallback(HighlightRulesChanged)));
private static void HighlightRulesChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
HighlightTextBox tb = (HighlightTextBox)sender;
tb.ApplyHighlights();
}
public HighlightTextBox() : base()
{
this.Loaded += HighlightTextBox_Loaded;
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
ApplyHighlights();
}
private void HighlightTextBox_Loaded(object sender, RoutedEventArgs e)
{
ApplyHighlights();
}
static HighlightTextBox()
{
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
public void ApplyHighlights()
{
this.TryRemoveAdorner<GenericAdorner>();
foreach(HighlightRule rule in HighlightRules)
{
if (!string.IsNullOrEmpty(rule.MatchText) && !string.IsNullOrEmpty(Text) &&
Text.ToLower().Contains(rule.MatchText.ToLower()))
{
if (base.ActualHeight != 0 && base.ActualWidth != 0)
{
int indexOf = 0;
do
{
indexOf = Text.IndexOf(rule.MatchText, indexOf);
if (indexOf == -1) break;
Rect rect = base.GetRectFromCharacterIndex(indexOf);
Rect backRect = base.GetRectFromCharacterIndex(indexOf + rule.MatchText.Length - 1, true);
this.TryAddAdorner<GenericAdorner>(new GenericAdorner(this, new Rectangle()
{ Height = rect.Height, Width = backRect.X - rect.X, Fill = rule.Brush, Opacity = 0.5 }));
indexOf++;
} while (true);
}
}
}
}
}
GenericAdorner / Helper Methods
public class GenericAdorner : Adorner
{
private readonly UIElement adorner;
private readonly Point point;
public GenericAdorner(UIElement targetElement, UIElement adorner, Point point) : base(targetElement)
{
this.adorner = adorner;
if (adorner != null)
{
AddVisualChild(adorner);
}
this.point = point;
}
protected override int VisualChildrenCount
{
get { return adorner == null ? 0 : 1; }
}
protected override Size ArrangeOverride(Size finalSize)
{
if (adorner != null)
{
adorner.Arrange(new Rect(point, adorner.DesiredSize));
}
return finalSize;
}
protected override Visual GetVisualChild(int index)
{
if (index == 0 && adorner != null)
{
return adorner;
}
return base.GetVisualChild(index);
}
}
public static void TryRemoveAdorner<T>(this UIElement element)
where T:Adorner
{
AdornerLayer layer = AdornerLayer.GetAdornerLayer(element);
if (layer != null)
layer.RemoveAdorners<T>(element);
}
public static void RemoveAdorners<T>(this AdornerLayer layer, UIElement element)
where T : Adorner
{
var adorners = layer.GetAdorners(element);
if (adorners == null) return;
for (int i = adorners.Length -1; i >= 0; i--)
{
if (adorners[i] is T)
layer.Remove(adorners[i]);
}
}
public static void TryAddAdorner<T>(this UIElement element, Adorner adorner)
where T: Adorner
{
AdornerLayer layer = AdornerLayer.GetAdornerLayer(element);
if (layer != null)
try
{
layer.Add(adorner);
}
catch (Exception) { }
}
public static bool HasAdorner<T>(this AdornerLayer layer, UIElement element) where T : Adorner
{
var adorners = layer.GetAdorners(element);
if (adorners == null) return false;
for (int i = adorners.Length - 1; i >= 0; i--)
{
if (adorners[i] is T)
return true;
}
return false;
}
public static void RemoveAdorners(this AdornerLayer layer, UIElement element)
{
var adorners = layer.GetAdorners(element);
if (adorners == null) return;
foreach (Adorner remove in adorners)
layer.Remove(remove);
}
The XAML
<local:HighlightTextBox FontFamily="Calibri" Foreground="Blue" FontSize="12" Text="Hello you!! And also hello to you!" TextWrapping="Wrap" Margin="5,3,0,0">
<local:HighlightTextBox.HighlightRules>
<local:HighlightRule Brush="Red" MatchText="you"/>
<local:HighlightRule Brush="Blue" MatchText="also"/>
</local:HighlightTextBox.HighlightRules>
</local:HighlightTextBox>
Appearance

WPF Listbox auto scroll while dragging

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;
}
}

Categories

Resources