RIch Text box Requirement in WPF - wpf

I have a requirement in WPF in which...
I want Rich text box to be used as dateTemplate for a listview for one particular column(which has remarks)
I have set of key word (add, search, location etc..)
In that column I want all those keywords to be highlighted in yellow colour.
I have another TextBox .If I type any word for searching in that text box and it exists in listview then it should be highlighted with green.
If I search the keyword which are in yellow then they should not be highlighted with green they should be in yellow colour.
If I have “added” word in which add is highlighted in yellow then when I search for “added” “add” should be in yellow and “ed” should be in green.
Any help is appreciated. I have tried several ways but failed at the last point above.
//Code snippet
// Xaml File
<Window x:Class="RichtextBoxhighlight.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:RichtextBoxhighlight"
Title="MainWindow" Height="406" Width="855"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.Resources>
<src:ContentPlainConverter x:Key="typeConverter" />
<src:TestConvertor x:Key="spaceRem"/>
<Style x:Key="RichTextBoxWithRoundedCorners" TargetType="{x:Type RichTextBox}">
<Style.Resources>
<Style x:Key="{x:Type FlowDocument}" TargetType="{x:Type FlowDocument}">
<Setter Property="OverridesDefaultStyle" Value="true"/>
</Style>
</Style.Resources>
</Style>
</Window.Resources>
<Grid>
<TextBox Margin="114,14,130,255" Name="txtText" />
<Grid>
<ListView Height="241" ItemsSource="{Binding CollRemarks}" HorizontalAlignment="Left" Margin="0,126,0,0" Name="listView1" VerticalAlignment="Top" Width="827" >
<ListView.View >
<GridView >
<GridViewColumn Header="Type" Width="80" DisplayMemberBinding="{Binding Path=type}"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=Operatorid}" Width="70">
<GridViewColumnHeader Tag="CreatedEmployeeId" Content="Operator"/>
</GridViewColumn>
<GridViewColumn Width="710">
<GridViewColumn.CellTemplate>
<DataTemplate>
<src:BindableRichTextBox Document ="{Binding Path=remarks, Converter= {StaticResource typeConverter}}" HighlightPhrase="{Binding ElementName=txtText, Path=Text,Converter={StaticResource spaceRem}}" IsReadOnly="True" Background="Transparent" BorderBrush="Transparent" BorderThickness="0"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
<GridViewColumnHeader Tag="CommentText" Content="Remark" />
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Grid>
</Window>
//BindableRichTextBox class :
namespace RichtextBoxhighlight
{
public class BindableRichTextBox : RichTextBox
{
public static string oldstring;
public static readonly DependencyProperty DocumentProperty =
DependencyProperty.Register("Document", typeof(FlowDocument),
typeof(BindableRichTextBox), new FrameworkPropertyMetadata
(null, new PropertyChangedCallback(OnDocumentChanged)));
public static readonly DependencyProperty HighlightPhraseProperty =
DependencyProperty.Register("HighlightPhrase", typeof(string),
typeof(BindableRichTextBox), new FrameworkPropertyMetadata(String.Empty, FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(UpdateHighlighting)));
public string HighlightPhrase
{
get { return (string)GetValue(HighlightPhraseProperty); }
set { SetValue(HighlightPhraseProperty, value); }
}
private static void UpdateHighlighting(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//invoked on text box search
ApplyHighlight(d as BindableRichTextBox);
ApplyAllHighlight(d as BindableRichTextBox);
}
// Gets or sets the Document of RichtextBox
public new FlowDocument Document
{
get
{
return (FlowDocument)this.GetValue(DocumentProperty);
}
set
{
this.SetValue(DocumentProperty, value);
}
}
// Method invoked on documnet formation.
public static void OnDocumentChanged(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
try
{
RichTextBox rtb = (RichTextBox)obj;
// ApplyHighlight(rtb as BindableRichTextBox);
rtb.Document = (FlowDocument)args.NewValue;
ApplyAllHighlight(rtb as BindableRichTextBox);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
/// <summary>
/// Implementation logic for Text Changed search highlight
/// </summary>
private static void ApplyHighlight(BindableRichTextBox tb)
{
try
{
if (tb != null && tb.Document != null)
{
string highlightPhrase = tb.HighlightPhrase;
// Frame the highlight word to skip them from getting highlighted by searchtext
string highlightWords = "(";
for (int i = 0; i < MainWindow.Highlightwords.Count; i++)
{
highlightWords += MainWindow.Highlightwords[i];
if (i != MainWindow.Highlightwords.Count - 1)
highlightWords += "|";
}
highlightWords += ")";
var start = tb.Document.ContentStart;
TextRange range = new TextRange(start, tb.Document.ContentEnd);
range.ClearAllProperties();
Regex ignore = new Regex(highlightWords, RegexOptions.Compiled | RegexOptions.IgnoreCase);
string Text = range.Text;
int index_match = 0;
if (highlightPhrase != string.Empty && highlightPhrase != null)
{
while (start != null && start.CompareTo(tb.Document.ContentEnd) < 0)
{
// Comparison to skip the key words in the column to be highlighted
if (start.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
{
index_match = Text.IndexOf(highlightPhrase, StringComparison.OrdinalIgnoreCase);
if (index_match >= 0)
{
//Ignore all Keyword highlight by incrementing start and moving to next match
MatchCollection matches = ignore.Matches(start.GetTextInRun(LogicalDirection.Forward));
if (matches != null)
{
for (int i = 0; i < matches.Count; i++)
{
if ((index_match >= matches[i].Index && index_match <= matches[i].Index + matches[i].Length))
{
start = start.GetPositionAtOffset(matches[i].Index + matches[i].Length, LogicalDirection.Forward);
Text = new TextRange(start.GetInsertionPosition(LogicalDirection.Forward), start.DocumentEnd).Text;
//Highlight the search text if part before keyword
if (highlightPhrase.Length > matches[i].Length)
{
var textrange = new TextRange(start.GetPositionAtOffset(0, LogicalDirection.Forward), start.GetPositionAtOffset(highlightPhrase.Length - matches[i].Length, LogicalDirection.Backward));
textrange.ApplyPropertyValue(TextElement.BackgroundProperty, new SolidColorBrush(Colors.Green));
}
if (start != null && start.CompareTo(tb.Document.ContentEnd) < 0)
{
index_match = Text.IndexOf(highlightPhrase, StringComparison.OrdinalIgnoreCase);
if (index_match >= 0)
{
matches = ignore.Matches(start.GetTextInRun(LogicalDirection.Forward));
if (matches != null && matches.Count > 0)
i--;
}
else
break;
}
}
else if ((highlightPhrase.Length - 1 >= matches[i].Index && highlightPhrase.Length + index_match <= matches[i].Index + matches[i].Length))
{
//Highlight the search text if part after keyword
var textrange = new TextRange(start.GetPositionAtOffset(index_match, LogicalDirection.Forward), start.GetPositionAtOffset(matches[i].Index, LogicalDirection.Backward));
textrange.ApplyPropertyValue(TextElement.BackgroundProperty, new SolidColorBrush(Colors.Green));
start = start.GetPositionAtOffset(matches[i].Index + matches[i].Length, LogicalDirection.Forward);
Text = new TextRange(start.GetInsertionPosition(LogicalDirection.Forward), start.DocumentEnd).Text;
if (start != null && start.CompareTo(tb.Document.ContentEnd) < 0)
{
index_match = Text.IndexOf(highlightPhrase, StringComparison.OrdinalIgnoreCase);
if (index_match >= 0)
{
matches = ignore.Matches(start.GetTextInRun(LogicalDirection.Forward));
if (matches != null && matches.Count > 0)
i--;
}
else
break;
}
}
}
}
if (index_match >= 0 && start != null && (start.CompareTo(tb.Document.ContentEnd) < 0))
{
//Highlight the search text typed if not part of Keywords
var textrange = new TextRange(start.GetPositionAtOffset(index_match, LogicalDirection.Forward), start.GetPositionAtOffset(index_match + highlightPhrase.Length, LogicalDirection.Backward));
textrange.ApplyPropertyValue(TextElement.BackgroundProperty, new SolidColorBrush(Colors.Green));
start = textrange.End;
Text = new TextRange(textrange.End, start.DocumentEnd).Text;
}
}
else
break;
}
if (start != null && start.CompareTo(tb.Document.ContentEnd) < 0)
start = start.GetNextContextPosition(LogicalDirection.Forward);
}
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
/// <summary>
/// Implementation logic for key words highlight
/// </summary>
private static void ApplyAllHighlight(BindableRichTextBox rtb)
{
try
{
if (rtb != null)
{
string highlightWords = "(";
for (int i = 0; i < MainWindow.Highlightwords.Count; i++)
{
highlightWords += MainWindow.Highlightwords[i];
if (i != MainWindow.Highlightwords.Count - 1)
highlightWords += "|";
}
highlightWords += ")";
Regex reg = new Regex(highlightWords, RegexOptions.Compiled | RegexOptions.IgnoreCase);
var start = rtb.Document.ContentStart;
while (start != null && start.CompareTo(rtb.Document.ContentEnd) < 0)
{
if (start.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text)
{
var match = reg.Match(start.GetTextInRun(LogicalDirection.Forward));
var textrange = new TextRange(start.GetPositionAtOffset(match.Index, LogicalDirection.Forward), start.GetPositionAtOffset(match.Index + match.Length, LogicalDirection.Backward));
textrange.ApplyPropertyValue(TextElement.BackgroundProperty, new SolidColorBrush(Colors.Yellow));
start = textrange.End;
}
start = start.GetNextContextPosition(LogicalDirection.Forward);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
//Content of list view remarks column and keywords to be highlighted
//Key word to be highlighted
Highlightwords = new List<string>();
Highlightwords.Add("mment");
Highlightwords.Add("ADD");
Highlightwords.Add("LOCATION");
Highlightwords.Add("arch");
// Remarks in list view
CollRemarks = new ObservableCollection<evcom>();
string remar = "search for added comments in location added";
CollRemarks.Add(new evcom { type = "Info", Operatorid = "1728", remarks = remar });
remar = "searched for added add comments in location added LOCATIONS";
CollRemarks.Add(new evcom { type = "Info", Operatorid = "1783", remarks = remar });

Related

TextBox doesn't stretch inside a Stackpanel

Please see my attached example code:
<StackPanel Width="300">
<StackPanel Orientation="Horizontal">
<Label Content="Label" Width="100" />
<TextBox Text="Content" />
</StackPanel>
</StackPanel>
How can I make the TextBox stretch to fill all the remaining space horizontally? HorizontalAlignment is not working the way I expect it.
The inner StackPanel will be a UserControl, so I can't just replace both StackPanels with a Grid.
Instead of using the inner StackPanel, you can use a DockPanel.
<StackPanel Width="300">
<DockPanel>
<Label Content="Label" Width="100" />
<TextBox Text="Content" />
</DockPanel>
</StackPanel>
I would recommend using DockPanel as the root control for your UserControl so that you could get the behavior you are looking for in its Child Controls. However if you absolutely need it to be based on a StackPanel then just use some code behind to set the textbox width dynamically.
Xaml:
<StackPanel Name="stpnlOuterControl" Width="300" SizeChanged="StackPanel_SizeChanged">
<StackPanel Orientation="Horizontal">
<Label Content="Test" Width="100"/>
<TextBox Name="txtbxTest" Text="Content"/>
</StackPanel>
</StackPanel>
Code Behind:
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
txtbxTest.Width = stpnlOuterControl.ActualWidth - 100
End Sub
Private Sub StackPanel_SizeChanged(sender As Object, e As SizeChangedEventArgs)
txtbxTest.Width = stpnlOuterControl.ActualWidth - 100
End Sub
Alternative way is to use custom StackPanel
https://github.com/SpicyTaco/SpicyTaco.AutoGrid/blob/master/src/AutoGrid/StackPanel.cs
MainWindow
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:VM="clr-namespace:WpfApplication4" >
<StackPanel Background="Red" Width="300">
<VM:StackPanel Orientation="Horizontal">
<Label Background="Blue" Content="Label" Width="100" />
<TextBox VM:StackPanel.Fill="Fill" Text="Content" />
</VM:StackPanel>
</StackPanel>
</Window>
Code for Custom Stack panel from SpicyTaco
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication4
{
public class StackPanel : Panel
{
protected override Size MeasureOverride(Size constraint)
{
UIElementCollection children = InternalChildren;
double parentWidth = 0;
double parentHeight = 0;
double accumulatedWidth = 0;
double accumulatedHeight = 0;
var isHorizontal = Orientation == Orientation.Horizontal;
var totalMarginToAdd = CalculateTotalMarginToAdd(children, MarginBetweenChildren);
for (int i = 0; i < children.Count; i++)
{
UIElement child = children[i];
if (child == null) { continue; }
// Handle only the Auto's first to calculate remaining space for Fill's
if (GetFill(child) != StackPanelFill.Auto) { continue; }
// Child constraint is the remaining size; this is total size minus size consumed by previous children.
var childConstraint = new Size(Math.Max(0.0, constraint.Width - accumulatedWidth),
Math.Max(0.0, constraint.Height - accumulatedHeight));
// Measure child.
child.Measure(childConstraint);
var childDesiredSize = child.DesiredSize;
if (isHorizontal)
{
accumulatedWidth += childDesiredSize.Width;
parentHeight = Math.Max(parentHeight, accumulatedHeight + childDesiredSize.Height);
}
else
{
parentWidth = Math.Max(parentWidth, accumulatedWidth + childDesiredSize.Width);
accumulatedHeight += childDesiredSize.Height;
}
}
// Add all margin to accumulated size before calculating remaining space for
// Fill elements.
if (isHorizontal)
{
accumulatedWidth += totalMarginToAdd;
}
else
{
accumulatedHeight += totalMarginToAdd;
}
var totalCountOfFillTypes = children
.OfType<UIElement>()
.Count(x => GetFill(x) == StackPanelFill.Fill
&& x.Visibility != Visibility.Collapsed);
var availableSpaceRemaining = isHorizontal
? Math.Max(0, constraint.Width - accumulatedWidth)
: Math.Max(0, constraint.Height - accumulatedHeight);
var eachFillTypeSize = totalCountOfFillTypes > 0
? availableSpaceRemaining / totalCountOfFillTypes
: 0;
for (int i = 0; i < children.Count; i++)
{
UIElement child = children[i];
if (child == null) { continue; }
// Handle all the Fill's giving them a portion of the remaining space
if (GetFill(child) != StackPanelFill.Fill) { continue; }
// Child constraint is the remaining size; this is total size minus size consumed by previous children.
var childConstraint = isHorizontal
? new Size(eachFillTypeSize,
Math.Max(0.0, constraint.Height - accumulatedHeight))
: new Size(Math.Max(0.0, constraint.Width - accumulatedWidth),
eachFillTypeSize);
// Measure child.
child.Measure(childConstraint);
var childDesiredSize = child.DesiredSize;
if (isHorizontal)
{
accumulatedWidth += childDesiredSize.Width;
parentHeight = Math.Max(parentHeight, accumulatedHeight + childDesiredSize.Height);
}
else
{
parentWidth = Math.Max(parentWidth, accumulatedWidth + childDesiredSize.Width);
accumulatedHeight += childDesiredSize.Height;
}
}
// Make sure the final accumulated size is reflected in parentSize.
parentWidth = Math.Max(parentWidth, accumulatedWidth);
parentHeight = Math.Max(parentHeight, accumulatedHeight);
var parent = new Size(parentWidth, parentHeight);
return parent;
}
protected override Size ArrangeOverride(Size arrangeSize)
{
UIElementCollection children = InternalChildren;
int totalChildrenCount = children.Count;
double accumulatedLeft = 0;
double accumulatedTop = 0;
var isHorizontal = Orientation == Orientation.Horizontal;
var marginBetweenChildren = MarginBetweenChildren;
var totalMarginToAdd = CalculateTotalMarginToAdd(children, marginBetweenChildren);
double allAutoSizedSum = 0.0;
int countOfFillTypes = 0;
foreach (var child in children.OfType<UIElement>())
{
var fillType = GetFill(child);
if (fillType != StackPanelFill.Auto)
{
if (child.Visibility != Visibility.Collapsed && fillType != StackPanelFill.Ignored)
countOfFillTypes += 1;
}
else
{
var desiredSize = isHorizontal ? child.DesiredSize.Width : child.DesiredSize.Height;
allAutoSizedSum += desiredSize;
}
}
var remainingForFillTypes = isHorizontal
? Math.Max(0, arrangeSize.Width - allAutoSizedSum - totalMarginToAdd)
: Math.Max(0, arrangeSize.Height - allAutoSizedSum - totalMarginToAdd);
var fillTypeSize = remainingForFillTypes / countOfFillTypes;
for (int i = 0; i < totalChildrenCount; ++i)
{
UIElement child = children[i];
if (child == null) { continue; }
Size childDesiredSize = child.DesiredSize;
var fillType = GetFill(child);
var isCollapsed = child.Visibility == Visibility.Collapsed || fillType == StackPanelFill.Ignored;
var isLastChild = i == totalChildrenCount - 1;
var marginToAdd = isLastChild || isCollapsed ? 0 : marginBetweenChildren;
Rect rcChild = new Rect(
accumulatedLeft,
accumulatedTop,
Math.Max(0.0, arrangeSize.Width - accumulatedLeft),
Math.Max(0.0, arrangeSize.Height - accumulatedTop));
if (isHorizontal)
{
rcChild.Width = fillType == StackPanelFill.Auto || isCollapsed ? childDesiredSize.Width : fillTypeSize;
rcChild.Height = arrangeSize.Height;
accumulatedLeft += rcChild.Width + marginToAdd;
}
else
{
rcChild.Width = arrangeSize.Width;
rcChild.Height = fillType == StackPanelFill.Auto || isCollapsed ? childDesiredSize.Height : fillTypeSize;
accumulatedTop += rcChild.Height + marginToAdd;
}
child.Arrange(rcChild);
}
return arrangeSize;
}
static double CalculateTotalMarginToAdd(UIElementCollection children, double marginBetweenChildren)
{
var visibleChildrenCount = children
.OfType<UIElement>()
.Count(x => x.Visibility != Visibility.Collapsed && GetFill(x) != StackPanelFill.Ignored);
var marginMultiplier = Math.Max(visibleChildrenCount - 1, 0);
var totalMarginToAdd = marginBetweenChildren * marginMultiplier;
return totalMarginToAdd;
}
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
"Orientation", typeof(Orientation), typeof(StackPanel),
new FrameworkPropertyMetadata(
Orientation.Vertical,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsMeasure));
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
public static readonly DependencyProperty MarginBetweenChildrenProperty = DependencyProperty.Register(
"MarginBetweenChildren", typeof(double), typeof(StackPanel),
new FrameworkPropertyMetadata(
0.0,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsMeasure));
public double MarginBetweenChildren
{
get { return (double)GetValue(MarginBetweenChildrenProperty); }
set { SetValue(MarginBetweenChildrenProperty, value); }
}
public static readonly DependencyProperty FillProperty = DependencyProperty.RegisterAttached(
"Fill", typeof(StackPanelFill), typeof(StackPanel),
new FrameworkPropertyMetadata(
StackPanelFill.Auto,
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsParentArrange |
FrameworkPropertyMetadataOptions.AffectsParentMeasure));
public static void SetFill(DependencyObject element, StackPanelFill value)
{
element.SetValue(FillProperty, value);
}
public static StackPanelFill GetFill(DependencyObject element)
{
return (StackPanelFill)element.GetValue(FillProperty);
}
}
public enum StackPanelFill
{
Auto, Fill, Ignored
}
}

Hiding the text of a TextBox control without using a PasswordBox

I am using a TextBox control for the user input in my Windows Phone 8.1 app.
How can I hide the characters as user gives input?
I am not using a PasswordBox because the defined InputScope is "Number" which is not possible in a PasswordBox.
While searching for a solution on the internet I found the only way by customizing the TextBox with the help of an UserControl.
Is there any easier way to do this without creating any UserControl?
Following is my code snippet:
In XAML page:
<TextBox Text="{Binding CardNo, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
MaxLength="17"
x:Name="CardNoTextBox"
InputScope="Number"
Margin="70,5"
PlaceholderText="Enter Your Card Number"
TextChanged="CardNoTextBox_TextChanged"
BorderBrush="Gray"
BorderThickness="2"
FontSize="20"/>
In code behind (xaml.cs):
private void CardNoTextBox_TextChanged(object sender, RoutedEventArgs routedEventArgs)
{
if (IsTextAllowed(CardNoTextBox.Text))
{
if (CardNoTextBox.Text.Length == 5)
{
if (CardNoTextBox.Text[4] != ' ')
{
string text = CardNoTextBox.Text.Insert(4, " ");
CardNoTextBox.Text = text;
CardNoTextBox.Select(CardNoTextBox.Text.Length, 0);
}
}
if (CardNoTextBox.Text.Length == 12)
{
if (CardNoTextBox.Text[11] != ' ')
{
string text = CardNoTextBox.Text.Insert(11, " ");
CardNoTextBox.Text = text;
CardNoTextBox.Select(CardNoTextBox.Text.Length, 0);
}
}
}
else
{
CardNoTextBox.Text = "";
}
}
After spending hours in finding an easier way I got an amazing solution. Hope this would help others too.
I simply added the following value to the FontFamily property of my TextBox control:
FontFamily="ms-appx:///Assets/PassDot.ttf#PassDot"
And gave the size of font 35,
FontSize="35"
This works just fine for my project.
I managed to create a custom TextBox, in which Text is *, but there is hiddenText that keeps the real string. Note that managing Caret position is not easy, because it changes due to some internal logic. Therefore, it is always at the end of the string. (Also note that you might need to handle some exceptions and bugs)
public class HiddenTextBox : TextBox
{
internal string hiddenText { get; private set; }
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Space)
addText(" ");
else if (e.Key == Key.Back)
removeText(true);
else if (e.Key == Key.Delete)
removeText(false);
else if (e.Key == Key.Return)
e.Handled = true;
base.OnPreviewKeyDown(e);
}
protected override void OnPreviewTextInput(TextCompositionEventArgs e)
{
addText(e.Text);
e.Handled = true;
}
void addText(string text)
{
hiddenText = hiddenText != null ? hiddenText.Insert(CaretIndex, text) : text;
update();
}
void removeText(bool back)
{
if (hiddenText == null || hiddenText.Length == 0 || (back==false && CaretIndex == hiddenText.Length))
return;
if (back)
hiddenText = hiddenText.Substring(0, CaretIndex - 1) + hiddenText.Substring(CaretIndex, hiddenText.Length - CaretIndex);
else
hiddenText = hiddenText.Substring(0, CaretIndex) + hiddenText.Substring(CaretIndex+1, hiddenText.Length - CaretIndex);
update();
}
void update()
{
StringBuilder star = new StringBuilder();
foreach (var s in hiddenText)
{
star.Append("*");
}
Text = star.ToString();
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
if (hiddenText != null)
CaretIndex += hiddenText.Length;
}
}

Zooming To Mouse Point With ScrollView and ViewBox in Wpf

I have some paths drawn to the screen in wpf. The coordinates being used are quite small so they have been made to fill the screen with a view box. I am now trying to implement pan and zoom functionality. I would like to be able to zoom to wherever the mouse is in relation to the ui (i.e zoomed screen center is equal to mouse coordinates). The current outcome is that the center of the screen when zoomed is not reflective of the exact mouse position on the ui.
If you want to see what is happening... Here is my current solution file.
Or
Heres some code:
View Xaml
<Grid Name="MasterGrid" DataContext="{StaticResource mainWindowViewModel}">
<ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible" Name="VisualisationScroller">
<Viewbox Name="VisualisationBox" Stretch="Fill" Loaded="VisualisationBox_Loaded">
<ItemsControl Name="CustomDrawingElement" ItemsSource="{Binding Trajectories}" Width="{Binding VisualisationWidth}" Height="{Binding VisualisationHeight}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="data:VisualisedTrajectory">
<Path Data = "{Binding PathData}" Stroke="Red" StrokeThickness="0.001" Fill="Transparent" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas
Background="DarkGray"
IsItemsHost="True">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1" />
<TranslateTransform />
</TransformGroup>
</ItemsControl.RenderTransform>
</ItemsControl>
</Viewbox>
</ScrollViewer>
</Grid>
View Model
public class MainWindowViewModel : BaseViewModel
{
public MainWindowViewModel()
{
VisualiseRawTrajectories();
}
private ObservableCollection<VisualisedTrajectory> _trajectories = new ObservableCollection<VisualisedTrajectory>();
public ObservableCollection<VisualisedTrajectory> Trajectories
{
get { return _trajectories; }
}
#region VisualisationDimensions
private double _visualisationWidth = 100;
public double VisualisationWidth
{
get { return _visualisationWidth; }
private set { _visualisationWidth = value; }
}
private double _visualisationHeight = 100;
public double VisualisationHeight
{
get { return _visualisationHeight; }
private set { _visualisationHeight = value; }
}
#endregion
public void VisualiseRawTrajectories()
{
var rand = new Random();
for (int i = 0; i < 5; i++)
{
var currentTrajectorySet = new List<Point>(); //each time through reinitialise
for (int j = 0; j < 5; j++)
{
currentTrajectorySet.Add(new Point(rand.NextDouble() * 0.5, rand.NextDouble() * 0.5)); //add a new point with max 0.5
if(j == 4)
{
currentTrajectorySet.Add(new Point(0.5, 0.5)); //for good measure :)
_trajectories.Add(new VisualisedTrajectory(CreatePathData(currentTrajectorySet)));
}
}
}
VisualisationHeight = 0.5;
VisualisationWidth = 0.5; //just for demonstration purposes
OnPropertyChanged("VisualisationHeight");
OnPropertyChanged("VisualisationWidth");
}
private Geometry CreatePathData(IList<Point> points)
{
var geometry = new StreamGeometry {FillRule = FillRule.EvenOdd};
using (StreamGeometryContext ctx = geometry.Open())
{
ctx.BeginFigure(points[0], false, false); //use the first index
ctx.PolyLineTo(points, true, true);
}
return (Geometry)geometry.GetAsFrozen();
}
}
View Code Behind
public MainWindow()
{
InitializeComponent();
VisualisationScroller.PreviewMouseWheel += OnPreviewMouseWheel;
}
private Point originalDimensions;
private void VisualisationBox_Loaded(object sender, RoutedEventArgs e)
{
Viewbox viewBox = sender as Viewbox;
viewBox.Width = viewBox.ActualWidth;
viewBox.Height = viewBox.ActualHeight;
originalDimensions = new Point(viewBox.ActualWidth, viewBox.ActualHeight);
}
#region Zoom
private int _numberDrawnItems = 0;
private void OnPreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
var zoomScale = new Point(CustomDrawingElement.RenderTransform.Value.M11,
CustomDrawingElement.RenderTransform.Value.M22); //gets the scale x and scale y
if (CustomDrawingElement != null && _numberDrawnItems != CustomDrawingElement.Items.Count) //if there was something draw to screen
{
_numberDrawnItems = CustomDrawingElement.Items.Count;
CustomDrawingElement.RenderTransformOrigin = new Point(0.5, 0.5); //if not set zoom from center
}
if (e.Delta > 0)
{
if (CustomDrawingElement != null)
{
VisualisationBox.Width = originalDimensions.X * (zoomScale.X + 1);
VisualisationBox.Height = originalDimensions.Y * (zoomScale.Y + 1);
var mousePosition = e.GetPosition(MasterGrid);
CustomDrawingElement.RenderTransformOrigin = new Point(mousePosition.X / MasterGrid.ActualWidth, mousePosition.Y / MasterGrid.ActualHeight);
CustomDrawingElement.RenderTransform = new MatrixTransform(zoomScale.X + 1, 0, 0, zoomScale.Y + 1, 0, 0);
}
}
if (e.Delta < 0)
{
if (zoomScale.X > 1 && zoomScale.Y > 1) //stops you from zooming out too much
{
if (CustomDrawingElement != null)
{
VisualisationBox.Width = VisualisationBox.Width - originalDimensions.X;
VisualisationBox.Height = VisualisationBox.Height - originalDimensions.Y;
CustomDrawingElement.RenderTransform = new MatrixTransform(zoomScale.X - 1, 0, 0, zoomScale.Y - 1, 0, 0);
}
}
}
e.Handled = true;
}
#endregion //Zooming code
}
Solved it changed the back code for zooming the View to:
if (e.Delta > 0)
{
if (CustomDrawingElement != null)
{
//zoom
_zoomScale++; //increase it now that we have zoomed
VisualisationBox.Width = _originalDimensions.X * (_zoomScale);
VisualisationBox.Height = _originalDimensions.Y * (_zoomScale);
ScrollerDimensions.Content = VisualisationScroller.ActualWidth + "x" + VisualisationScroller.ActualHeight;
BoxDimensions.Content = VisualisationBox.ActualWidth + "x" + VisualisationBox.ActualHeight;
var mousePosition = e.GetPosition(MasterGrid);
mousePosition = MasterGrid.TransformToVisual(VisualisationBox).Transform(mousePosition);
ScrolledPoint.Content = mousePosition.X + "," + mousePosition.Y;
VisualisationScroller.ScrollToHorizontalOffset(mousePosition.X);
VisualisationScroller.ScrollToVerticalOffset(mousePosition.Y);
}
}
if (e.Delta < 0)
{
if (_zoomScale > 1) //stops you from zooming out too much
{
if (CustomDrawingElement != null)
{
var mousePosition = e.GetPosition(MasterGrid);
_zoomScale -= 1; //decrease the zoom
VisualisationBox.Width = VisualisationBox.Width - _originalDimensions.X;
VisualisationBox.Height = VisualisationBox.Height - _originalDimensions.Y;
mousePosition = MasterGrid.TransformToVisual(VisualisationBox).Transform(mousePosition);
mousePosition = new Point(mousePosition.X - _originalDimensions.X, mousePosition.Y - _originalDimensions.Y);
VisualisationScroller.ScrollToHorizontalOffset(mousePosition.X);
VisualisationScroller.ScrollToVerticalOffset(mousePosition.Y);
}
}
}
e.Handled = true;
If anyone is interested HERE is the finished solution file with panning implemented too.

WPF Generate TextBlock Inlines

I have a GridView and in one of the GridViewColumns i want to generate a text like this:
textBlock.Text = string.Format("{0} is doing {1} .......", a, b);
but a and b (Properties of an item in the View) should not just be represented as plain text, but as a Hyperlink for example.
(Also: The format text should depend on the type of the item)
How can i generate the TextBlocks text in that way? (for localization)
The Question is more: Should i write something on my own or is there an easy way provided by the framework?
An old question, but I find accepted answer an absolute overkill. You don't need to parse the formatted text at all! Just wrap it up in Span element and you are done.
public class Attached
{
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
"FormattedText",
typeof(string),
typeof(Attached),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure, FormattedTextPropertyChanged));
public static void SetFormattedText(DependencyObject textBlock, string value)
{
textBlock.SetValue(FormattedTextProperty, value);
}
public static string GetFormattedText(DependencyObject textBlock)
{
return (string)textBlock.GetValue(FormattedTextProperty);
}
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBlock = d as TextBlock;
if (textBlock == null)
{
return;
}
var formattedText = (string)e.NewValue ?? string.Empty;
formattedText = string.Format("<Span xml:space=\"preserve\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">{0}</Span>", formattedText);
textBlock.Inlines.Clear();
using (var xmlReader = XmlReader.Create(new StringReader(formattedText)))
{
var result = (Span)XamlReader.Load(xmlReader);
textBlock.Inlines.Add(result);
}
}
}
Now you can use the FormattedText attached property either in your code:
string inlineExpression = "<Run Style=\"Theme.GrayText\">Once I saw a little bird, go hop, hop, hop.</Run>";
Attached.SetFormattedText(myTextBlock1, inlineExpression);
More importantly, straight from the XAML:
<TextBlock ns:Attached.FormattedText="{Binding Content}" />
Where ns is the namespace you defined the Attached class in.
Recently i came across the same problem. So i decided to implement an attached property for TextBlock which takes a value of string type and then populates the Inlines collection on the fly. You can simply set the property value to something like this:
string inlineExpression = "Once i saw a little <bold>bird</bold>, <span>go <bold>hop, hop, hop</bold></span>.";
InlineExpression.SetInlineExpression(myTextBlock1, inlineExpression);
The styles are also supported:
string inlineExpression = "<run style="Theme.GrayText">Once i saw a little bird, go hop, hop, hop.</run>";
InlineExpression.SetInlineExpression(myTextBlock1, inlineExpression);
You can also use this attached property in XAML in a standard way.
Here is the code of the class which exposes this property:
public class InlineExpression
{
public static readonly DependencyProperty InlineExpressionProperty = DependencyProperty.RegisterAttached(
"InlineExpression", typeof(string), typeof(TextBlock), new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.AffectsMeasure));
public static void SetInlineExpression(TextBlock textBlock, string value)
{
textBlock.SetValue(InlineExpressionProperty, value);
textBlock.Inlines.Clear();
if (string.IsNullOrEmpty(value))
return;
var descriptions = GetInlineDescriptions(value);
if (descriptions.Length == 0)
return;
var inlines = GetInlines(textBlock, descriptions);
if (inlines.Length == 0)
return;
textBlock.Inlines.AddRange(inlines);
}
public static string GetInlineExpression(TextBlock textBlock)
{
return (string)textBlock.GetValue(InlineExpressionProperty);
}
enum InlineType
{
Run,
LineBreak,
Span,
Bold,
Italic,
Hyperlink,
Underline
}
class InlineDescription
{
public InlineType Type { get; set; }
public string Text { get; set; }
public InlineDescription[] Inlines { get; set; }
public string StyleName { get; set; }
}
private static Inline[] GetInlines(FrameworkElement element, IEnumerable<InlineDescription> inlineDescriptions)
{
var inlines = new List<Inline>();
foreach (var description in inlineDescriptions)
{
var inline = GetInline(element, description);
if (inline != null)
inlines.Add(inline);
}
return inlines.ToArray();
}
private static Inline GetInline(FrameworkElement element, InlineDescription description)
{
Style style = null;
if (!string.IsNullOrEmpty(description.StyleName))
{
style = element.FindResource(description.StyleName) as Style;
if (style == null)
throw new InvalidOperationException("The style '" + description.StyleName + "' cannot be found");
}
Inline inline = null;
switch (description.Type)
{
case InlineType.Run:
var run = new Run(description.Text);
inline = run;
break;
case InlineType.LineBreak:
var lineBreak = new LineBreak();
inline = lineBreak;
break;
case InlineType.Span:
var span = new Span();
inline = span;
break;
case InlineType.Bold:
var bold = new Bold();
inline = bold;
break;
case InlineType.Italic:
var italic = new Italic();
inline = italic;
break;
case InlineType.Hyperlink:
var hyperlink = new Hyperlink();
inline = hyperlink;
break;
case InlineType.Underline:
var underline = new Underline();
inline = underline;
break;
}
if (inline != null)
{
var span = inline as Span;
if (span != null)
{
var childInlines = new List<Inline>();
foreach (var inlineDescription in description.Inlines)
{
var childInline = GetInline(element, inlineDescription);
if (childInline == null)
continue;
childInlines.Add(childInline);
}
span.Inlines.AddRange(childInlines);
}
if (style != null)
inline.Style = style;
}
return inline;
}
private static InlineDescription[] GetInlineDescriptions(string inlineExpression)
{
if (inlineExpression == null)
return new InlineDescription[0];
inlineExpression = inlineExpression.Trim();
if (inlineExpression.Length == 0)
return new InlineDescription[0];
inlineExpression = inlineExpression.Insert(0, #"<root>");
inlineExpression = inlineExpression.Insert(inlineExpression.Length, #"</root>");
var xmlTextReader = new XmlTextReader(new StringReader(inlineExpression));
var xmlDocument = new XmlDocument();
xmlDocument.Load(xmlTextReader);
var rootElement = xmlDocument.DocumentElement;
if (rootElement == null)
return new InlineDescription[0];
var inlineDescriptions = new List<InlineDescription>();
foreach (XmlNode childNode in rootElement.ChildNodes)
{
var description = GetInlineDescription(childNode);
if (description == null)
continue;
inlineDescriptions.Add(description);
}
return inlineDescriptions.ToArray();
}
private static InlineDescription GetInlineDescription(XmlNode node)
{
var element = node as XmlElement;
if (element != null)
return GetInlineDescription(element);
var text = node as XmlText;
if (text != null)
return GetInlineDescription(text);
return null;
}
private static InlineDescription GetInlineDescription(XmlElement element)
{
InlineType type;
var elementName = element.Name.ToLower();
switch (elementName)
{
case "run":
type = InlineType.Run;
break;
case "linebreak":
type = InlineType.LineBreak;
break;
case "span":
type = InlineType.Span;
break;
case "bold":
type = InlineType.Bold;
break;
case "italic":
type = InlineType.Italic;
break;
case "hyperlink":
type = InlineType.Hyperlink;
break;
case "underline":
type = InlineType.Underline;
break;
default:
return null;
}
string styleName = null;
var attribute = element.GetAttributeNode("style");
if (attribute != null)
styleName = attribute.Value;
string text = null;
var childDescriptions = new List<InlineDescription>();
if (type == InlineType.Run || type == InlineType.LineBreak)
{
text = element.InnerText;
}
else
{
foreach (XmlNode childNode in element.ChildNodes)
{
var childDescription = GetInlineDescription(childNode);
if (childDescription == null)
continue;
childDescriptions.Add(childDescription);
}
}
var inlineDescription = new InlineDescription
{
Type = type,
StyleName = styleName,
Text = text,
Inlines = childDescriptions.ToArray()
};
return inlineDescription;
}
private static InlineDescription GetInlineDescription(XmlText text)
{
var value = text.Value;
if (string.IsNullOrEmpty(value))
return null;
var inlineDescription = new InlineDescription
{
Type = InlineType.Run,
Text = value
};
return inlineDescription;
}
}
In XAML you could do something like this:
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<Hyperlink NavigateUri="{Binding AUri}">
<Run Text="{Binding A}"/>
</Hyperlink>
<Run Text=" is doing "/>
<Hyperlink NavigateUri="{Binding BUri}">
<Run Text="{Binding B}"/>
</Hyperlink>
</TextBlock>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
In code behind the same thing can be done but i would not recommend it since it involves using FrameworkElementFactories.

In a WPF Grid, how can I find the Row & Column at the mouse location?

I have a WPF Grid with some rows and columns, e.g.
<Grid Name="myGrid" MouseMove="OnMouseMove">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
</Grid>
With a handler for MouseMove in the .cs file, e.g.
private void OnMouseMove(object sender, MouseEventArgs e)
{
var position = e.GetPosition(myGrid);
// What row & col is the mouse over?
}
I want to be able to find which row and column in the grid the mouse is over, is this possible?
[Note: this is a simplified version of the problem, so it looks a little odd to present it in this way - it's part of some drag & drop between grids functionality]
I hope you have already find a solution. I faced to the same problem and I found this unanswered post. So I think the next one will be happy to find this solution :
private void OnMouseMove(object sender, MouseEventArgs e)
{
var element = (UIElement)e.Source;
int c = Grid.GetColumn(element);
int r = Grid.GetRow(element);
}
This the following can be done (but shouldn't be used on larger Grids). It is only for Rows, but a second loop can be applied to the columns...
The answer of Nicolas only works if the grid contains some UIElements (whose position in the grid are determined - I guess that would also cause trouble if an element spans several rows/columns).
private void ItemsGrid_MouseMove(object sender, MouseEventArgs e)
{
double y = e.GetPosition(ItemsGrid).Y;
double start = 0.0;
int row = 0;
foreach(RowDefinition rd in ItemsGrid.RowDefinitions)
{
start += rd.ActualHeight;
if (y < start)
{
break;
}
row++;
}
System.Diagnostics.Debug.WriteLine("Row : " + row);
}
public static class GridExtentions
{
public static T Parent<T>(this DependencyObject root) where T : class
{
if (root is T) { return root as T; }
DependencyObject parent = VisualTreeHelper.GetParent(root);
return parent != null ? parent.Parent<T>() : null;
}
public static Point GetColumnRow(this Grid obj, Point relativePoint) { return new Point(GetColumn(obj, relativePoint.X), GetRow(obj, relativePoint.Y)); }
private static int GetRow(Grid obj, double relativeY) { return GetData(obj.RowDefinitions, relativeY); }
private static int GetColumn(Grid obj, double relativeX) { return GetData(obj.ColumnDefinitions, relativeX); }
private static int GetData<T>(IEnumerable<T> list, double value) where T : DefinitionBase
{
var start = 0.0;
var result = 0;
var property = typeof(T).GetProperties().FirstOrDefault(p => p.Name.StartsWith("Actual"));
if (property == null) { return result; }
foreach (var definition in list)
{
start += (double)property.GetValue(definition);
if (value < start) { break; }
result++;
}
return result;
}
}
Usage:
protected override void OnMouseDown(MouseButtonEventArgs e)
{
base.OnMouseDown(e);
var hit = VisualTreeHelper.HitTest(this, e.GetPosition(this));
if (hit == null) { return; }
var grid = hit.VisualHit.Parent<Grid>();
if (grid == null) { return; }
var gridPosition = grid.GetColumnRow(e.GetPosition(grid));
MessageBox.Show(string.Format("Grid location Row: {1} Column: {0}", gridPosition.X, gridPosition.Y));
}
replace Grid.ColumnDefinitions with reference to Grid component
int GetColumn(double point)
{
int index = 0;
foreach(var column in Grid.ColumnDefinitions)
{
if(point > column.Offset && point < (column.Offset + column.ActualWidth))
return index;
index++;
}
return 0;
}
Hope can help. it's working well for my app
public class GridCell
{
public int GridRow { get; set; }
public int GridCol { get; set; }
}
private void Window_MouseMove(object sender, MouseEventArgs e)
{
Point point = e.GetPosition(this.myGrid);
Grid gridTarget = GetLastedGridContainPoint(point, this.myGrid);
Point pointTarget = this.myGrid.TranslatePoint(point, gridTarget);
GridCell cell = GetGridCellContainPoint(pointTarget, gridTarget);
}
private bool IsPointInGrid(Point relativePoint, Grid grid)
{
if (relativePoint.X < 0 || relativePoint.X > grid.ActualWidth ||
relativePoint.Y < 0 || relativePoint.Y > grid.ActualHeight)
{
return false;
}
return true;
}
private Grid GetLastedGridContainPoint(Point relativePoint, Grid gridParent)
{
Grid gridReturn = null;
if (gridParent.Children.Count > 0)
{
Point relativeChildPoint;
foreach (UIElement e in gridParent.Children)
{
if (e is Grid)
{
relativeChildPoint = gridParent.TranslatePoint(relativePoint, (e as Grid));
gridReturn = GetLastedGridContainPoint(relativeChildPoint, (e as Grid));
if (gridReturn != null)
{
break;
}
}
}
}
if (gridReturn == null)
{
if (IsPointInGrid(relativePoint, gridParent))
{
gridReturn = gridParent;
}
}
return gridReturn;
}
private GridCell GetGridCellContainPoint(Point relativePoint, Grid gridTarget)
{
if (!IsPointInGrid(relativePoint, gridTarget))
{
return null;
}
GridCell cell = new GridCell();
double dbStart = 0;
double dbEnd = 0;
if (gridTarget.ColumnDefinitions.Count > 0)
{
for (int i = 0; i < gridTarget.ColumnDefinitions.Count; i++)
{
dbStart = dbEnd;
dbEnd += gridTarget.ColumnDefinitions[i].ActualWidth;
if (relativePoint.X >= dbStart && relativePoint.X < dbEnd)
{
cell.GridCol = i;
break;
}
}
}
dbStart = 0;
dbEnd = 0;
if (gridTarget.RowDefinitions.Count > 0)
{
for (int i = 0; i < gridTarget.RowDefinitions.Count; i++)
{
dbStart = dbEnd;
dbEnd += gridTarget.RowDefinitions[i].ActualHeight;
if (relativePoint.Y >= dbStart && relativePoint.Y < dbEnd)
{
cell.GridRow = i;
break;
}
}
}
return cell;
}
I had exactly your same problem and I am also using FluidKit. I'm trying to build a form designer where you could drag a control from a Toolbox and drop it into a Grid cell. Here is how I solved it:
I created a grid with two dummy Rectangles in the first row:
<Grid Name="myCanvas" ShowGridLines="True" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle DragDrop:DragDropManager.DropTargetAdvisor="{StaticResource targetAdvisor1}"
Grid.Row="0" Grid.Column="0" Width="200" Height="100" Fill="Blue" Stroke="Black" StrokeThickness="4" />
<Rectangle DragDrop:DragDropManager.DropTargetAdvisor="{StaticResource targetAdvisor2}"
Grid.Row="0" Grid.Column="1" Width="200" Height="100" Fill="Red" Stroke="Black" StrokeThickness="4" />
</Grid>
It looks as follows:
Notice that I defined a DropTargetAdvisor for each of my rectangles, now the logic is as follows:
You drag/drop a Control from the Toolbox into a Cell
The OnDropCompleted method in the DropTargetAdvisor will then remove the rectangle where you're dropping the control and will get its coordinates from the Grid.Row and Grid.Column Attached properties.
Once you have the coordinates you can set those Attached properties to your dragged control and add it to your grid.
Here is my DefaultDropTargetAdvisor.OnDropCompleted method:
public void OnDropCompleted(IDataObject obj, Point dropPoint)
{
UIElement dragged_control = ExtractElement(obj);
//Get the current Rectangle
FrameworkElement fe = TargetUI as FrameworkElement;
//Get parent Grid from this Rectangle
Grid g = fe.Parent as Grid;
//Get row and columns of this Rectangle
int row = Grid.GetRow(TargetUI);
int col = Grid.GetColumn(TargetUI);
//Remove Rectangle
g.Children.Remove(TargetUI);
//Set row and column for the dragged control
Grid.SetRow(dragged_control, row);
Grid.SetColumn(dragged_control, col);
//Add dragged control to Grid in that row/col
g.Children.Add(dragged_control);
}
For a Telerik RadGridView, the best approach if the grid doesn't contain UI elements is to use ChildrenOfType<> in a Linq expression with IsMouseOver.
private void myGridView_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
MyCustomClass myClass = null;
var rows = this.myGridView.ChildrenOfType<GridViewRow>().Where(r => r.IsMouseOver == true);
foreach (var row in rows)
{
if (row is GridViewNewRow) break;
GridViewRow gvr = (GridViewRow)row;
myClass = (MyCustomClass)gvr.Item;
}
// do something with myClass here if we have found a row under mouse
}
Andre's answer has a minor mistake, as the coordinates obtained do not account for the row and column headers in the DataGrid. At least this was the case when I implemented the solution in Visual Basic.
You can also modify the examples shown to account for a large DataGrid. It seems to me that the limitation there is based on the scrolled view, so I show two implementations of this correction:
Private Sub myGrid_MouseMove(sender As Object, e As MouseEventArgs) Handles myGrid.MouseMove
Dim total As Double
Dim myScrollViewer As ScrollViewer = FindVisualChild(Of ScrollViewer)(myGrid)
Dim cursorPositionX = e.GetPosition(myGrid).X
Dim columnIndex As Integer = -1
total = 0
'Horizontal offset'
Dim rowHeaders As DataGridRowHeader = FindVisualChild(Of DataGridRowHeader)(myGrid)
cursorPositionX -= (rowHeaders.ActualWidth - myScrollViewer.HorizontalOffset)
For Each column As DataGridColumn In myGrid.Columns
If cursorPositionX < total Then Exit For
columnIndex += 1
total += column.Width.DisplayValue
Next
Dim cursorPositionY = e.GetPosition(myGrid).Y
Dim rowIndex As Integer = -1
total = 0
'Vertical offset'
Dim originalOffset As Double = myScrollViewer.VerticalOffset
Dim colHeadersPresenter As DataGridColumnHeadersPresenter = FindVisualChild(Of DataGridColumnHeadersPresenter)(myGrid)
cursorPositionY -= colHeadersPresenter.ActualHeight
For Each row As System.Data.DataRowView In myGrid.Items
If cursorPositionY < total Then Exit For
rowIndex += 1
Dim dgRow As DataGridRow = GetRowByIndex(myGrid, rowIndex)
total += dgRow.ActualHeight
'GetRowByIndex will scroll the view to bring the DataGridRow of interest into view, which throws off the counter. This adjusts for that'
myGrid.UpdateLayout()
If Not myScrollViewer.VerticalOffset = originalOffset Then myGrid.ScrollIntoView(myGrid.Items(CInt(myScrollViewer.ViewportHeight + originalOffset - 1)))
myGrid.UpdateLayout()
If myScrollViewer.VerticalOffset > rowIndex Then cursorPositionY += dgRow.ActualHeight
Next
End Sub
Note that the ScrollViewer.HorizontalOffset Property returns a value in Device Independent Pixels, so I merely offset my location once before looping through the columns.
Note that the ScrollViewer.VerticalOffset Property returns the number of items if CanContentScroll = True. So in my example, on each loop I offset the counter by the height of one item (the DataGridRow). If CanContentScroll = False, then it can be handled as in the case of the column index loop.
I couldn't find row definitions in the DataGrid for .Net 4.0 in Visual Basic, but the following supporting function helps for obtaining the DataGridRow:
Function GetRowByIndex(ByVal p_dataGrid As DataGrid,
ByVal p_index As Integer) As DataGridRow
Dim row As DataGridRow
row = CType(p_dataGrid.ItemContainerGenerator.ContainerFromIndex(p_index), DataGridRow)
If IsNothing(row) Then
'May be virtualized, bring into view and try again.'
p_dataGrid.UpdateLayout()
p_dataGrid.ScrollIntoView(p_dataGrid.Items(p_index))
row = CType(p_dataGrid.ItemContainerGenerator.ContainerFromIndex(p_index), DataGridRow)
End If
Return row
End Function
And the FindVisualChild function in Visual Basic:
Function FindVisualChild(Of childItem As DependencyObject)(ByVal p_obj As DependencyObject) As childItem
For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(p_obj) - 1
Dim child As DependencyObject = VisualTreeHelper.GetChild(p_obj, i)
If child IsNot Nothing AndAlso TypeOf child Is childItem Then
Return CType(child, childItem)
Else
Dim childOfChild As childItem = FindVisualChild(Of childItem)(child)
If childOfChild IsNot Nothing Then
Return childOfChild
End If
End If
Next i
Return Nothing
End Function

Resources