Chop the text and display three dots in PropertyGrid of winforms - winforms

I would like to cut the extra text and display three dots(...) and when user clicks on the cell, everthing has to be displayed. how to calculate the width of the property grid cell and cut the text. Any help will be grateful.
Pictures are attached for explanation
Instead of this
I would like to achieve this
and it should vary according to the cell size

The property grid does not allow that and you cannot customize it to do so using any official way.
However, here is some sample code that seems to work. It uses a TypeConverter to reduce the value from the grid's size.
Use at your own risk as it relies on PropertyGrid's internal methods and may have an impact on performance, since it requires a refresh on the whole grid on each resize.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
// note this may have an impact on performance
propertyGrid1.SizeChanged += (sender, e) => propertyGrid1.Refresh();
var t = new Test();
t.MyStringProperty = "The quick brown fox jumps over the lazy dog";
propertyGrid1.SelectedObject = t;
}
}
public class AutoSizeConverter : TypeConverter
{
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (value == null)
return null;
// small trick to get PropertyGrid control (view) from context
var view = (Control)context.GetService(typeof(IWindowsFormsEditorService));
// bigger trick (hack) to get value column width & font
int width = (int)view.GetType().GetMethod("GetValueWidth").Invoke(view, null);
var font = (Font)view.GetType().GetMethod("GetBoldFont").Invoke(view, null); // or GetBaseFont
// note: the loop is not super elegant and may probably be improved in terms of performance using some of the other TextRenderer overloads
string s = value.ToString();
string ellipsis = s;
do
{
var size = TextRenderer.MeasureText(ellipsis, font);
if (size.Width < width)
return ellipsis;
s = s.Substring(0, s.Length - 1);
ellipsis = s + "...";
}
while (true);
}
}
public class Test
{
// we use a custom type converter
[TypeConverter(typeof(AutoSizeConverter))]
public string MyStringProperty { get; set; }
}
Here is the result (supports resize):

Related

GMAP.NET adding labels underneath markers

I have just started using gmap.net and I was looking for the functionality of adding labels under the markers. I see there's tooltips but I would like to have a constant label under my marker with a one word description.
I searched for docs or other answers but I cannot find anything which leads me to believe that it is not implemented. If someone can verify this I would appreciate it.
You need to create your own custom marker.
Based on the source of GMapMarker and the derived GMarkerGoogle I came up with this simplified example:
public class GmapMarkerWithLabel : GMapMarker, ISerializable
{
private Font font;
private GMarkerGoogle innerMarker;
public string Caption;
public GmapMarkerWithLabel(PointLatLng p, string caption, GMarkerGoogleType type)
: base(p)
{
font = new Font("Arial", 14);
innerMarker = new GMarkerGoogle(p, type);
Caption = caption;
}
public override void OnRender(Graphics g)
{
if (innerMarker != null)
{
innerMarker.OnRender(g);
}
g.DrawString(Caption, font, Brushes.Black, new PointF(0.0f, innerMarker.Size.Height));
}
public override void Dispose()
{
if(innerMarker != null)
{
innerMarker.Dispose();
innerMarker = null;
}
base.Dispose();
}
#region ISerializable Members
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
}
protected GmapMarkerWithLabel(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
#endregion
}
Usage (assuming a GMap instance named gm):
GMapOverlay markerOverlay = new GMapOverlay("markers");
gm.Overlays.Add(markerOverlay);
var labelMarker = new GmapMarkerWithLabel(new PointLatLng(53.3, 9), "caption text", GMarkerGoogleType.blue);
markerOverlay.Markers.Add(labelMarker)
I'll answer here because this is the first question that pops up when looking to display a text marker for the WPF GMAP.NET library. Displaying a text marker with the WPF version of the library is actually much easier than in WinForms, or at least than the accepted answer.
The GMapMarker in WPF has a Shape property of type UIElement, which means you can provide a System.Windows.Controls.TextBlock object to display a text marker :
Markers.Add(new GMapMarker(new PointLatLng(latitude, longitude))
{
Shape = new System.Windows.Controls.TextBlock(new System.Windows.Documents.Run("Label"))
});
Since the marker displays the top left portion of the shape at the given position, you can play with the GMapMarker.Offset property to adjust the text position according to its dimensions. For instance, if you want the text to be horizontally centered on the marker's position :
var textBlock = new TextBlock(new Run("Label"));
textBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
textBlock.Arrange(new Rect(textBlock.DesiredSize));
Markers.Add(new GMapMarker(new PointLatLng(request.Latitude, request.Longitude))
{
Offset = new Point(-textBlock.ActualWidth / 2, 0),
Shape = textBlock
});
The solution to get the TextBlock's dimensions was quickly taken from this question, so if you need a more accurate way of getting the block's dimensions to play with the offset I suggest you start from there.

How to highlight the word(s) with bold in text block for specified keyword using the attached property

The objective is to bold the word(s) of text in Textblock with matching input keyword.
For example: Stackoverflow is a very helpful, keep using Stackoverflow to sharpen your skills.
When the keyword is: Stackoverflow it should now display as
Stackoverflow is a very helpful, keep using Stackoverflow to sharpen your skills.
I tried to achieve the same objective using attached property. Below is the snap code of the same
public class HighLightKeyWord : DependencyObject
{
//This word is used to specify the word to highlight
public static readonly DependencyProperty BoldWordProperty = DependencyProperty.RegisterAttached("BoldWord", typeof(string), typeof(HighLightKeyWord),
new PropertyMetadata(string.Empty, OnBindingTextChanged));
public static string GetBoldWord(DependencyObject obj)
{
return (string)obj.GetValue(BoldWordProperty);
}
public static void SetBoldWord(DependencyObject obj, string value)
{
obj.SetValue(BoldWordProperty, value);
}
private static void OnBindingTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var _Key = e.NewValue as string;
var textblk = d as TextBlock;
string SourceText = textblk.Text;
SetBold(_Key, textblk, SourceText);
}
private static void SetBold(string key, TextBlock text, string SourceText)
{
text.Inlines.Clear();
var words = SourceText.Split(' ');
for (int i = 0; i < words.Length; i++)
{
var word = words[i];
var inline = new Run() { Text = word + ' ' };
if (String.Compare(word, key, StringComparison.CurrentCultureIgnoreCase) == 0)
{
inline.FontWeight = FontWeights.Bold;
}
text.Inlines.Add(inline);
}
}
}
// Bind object in Main
StackOverFlow stkovrflw = new StackOverFlow();
stkovrflw.Text = "Stackoverflow is a very helpful,keep using Stackoverflow to sharpen your skills.";
stkovrflw.KeyWord = "Stackoverflow";
this.DataContext = stkovrflw;
In Xaml I binded the value as
<TextBlock Text="{Binding Path=Text}" loc:HighLightKeyWord.BoldWord="{Binding Path=KeyWord}" />
Above code is working fine, however when I directly sets the HighlightText property in the xaml instead through data binding, Text property of the Text block is getting empty in the OnBindingTextChanged method and this method is called only once when dependency property is set .
I used this design based on Spellchecker concept so that other teamates can reuse my attached property in their projects.
Can anyone suggest how to fix the problem?
Whether you want to modify a TextBlock FontWeight attribute or another display property, I've written the following static method and found it very useful. (Note: The method illustrates highlighting text by modifying the Foreground property. The same principle can be used for any other TextBlock display attribute.)
static Brush DefaultHighlight = new SolidColorBrush(Colors.Red);
public static void SetTextAndHighlightSubstring(this TextBlock targetTextBlock, string sourceText, string subString, Brush highlight = null)
{
if (targetTextBlock == null || String.IsNullOrEmpty(sourceText) || String.IsNullOrEmpty(subString))
return;
targetTextBlock.Text = "";
var subStringPosition = sourceText.ToUpper().IndexOf(subString);
if (subStringPosition == -1)
{
targetTextBlock.Inlines.Add(new Run { Text = sourceText });
return;
}
var subStringLength = subString.Length;
var header = sourceText.Substring(0, subStringPosition);
subString = sourceText.Substring(subStringPosition, subStringLength);
var trailerLength = sourceText.Length - (subStringPosition + subStringLength);
var trailer = sourceText.Substring(subStringPosition + subStringLength, trailerLength);
targetTextBlock.Inlines.Add(new Run { Text = header });
targetTextBlock.Inlines.Add(new Run { Text = subString, Foreground = highlight ?? DefaultHighlight });
targetTextBlock.Inlines.Add(new Run { Text = trailer });
}
You can call this method using the TextBlock extension syntax like so:
TextBlockTitle.SetTextAndHighlightSubstring(_categoryItem.ItemText, _searchText);
In this example, the following display resulted:
I recently ran into the same problem with the broken text binding. I realize this is an old question, but figured I'd share my finding anyway.
Basically Text property's data-binding is severed the moment Inlines.Clear is called. The way I got around this problem was by adding an internal text dependency property that replicated text's binding so subsequent text changes would continue to trigger highlighting.
Try adding something like this before clearing text.Inlines.
protected static string GetInternalText(DependencyObject obj)
{
return (string)obj.GetValue(InternalTextProperty)
}
protected static void SetInternalText(DependencyObject obj, string value)
{
obj.SetValue(InternalTextProperty, value);
}
// Using a DependencyProperty as the backing store for InternalText. This enables animation, styling, binding, etc...
protected static readonly DependencyProperty InternalTextProperty =
DependencyProperty.RegisterAttached("InternalText", typeof(string),
typeof(HighlightableTextBlock), new PropertyMetadata(string.Empty, OnInternalTextChanged));
private static void SetBold(string key, TextBlock text, string SourceText)
{
//Add the following code to replicate text binding
if (textblock.GetBindingExpression(HighlightableTextBlock.InternalTextProperty) == null)
{
var textBinding = text.GetBindingExpression(TextBlock.TextProperty);
if (textBinding != null)
{
text.SetBinding(HighLightKeyWord.InternalTextProperty, textBinding.ParentBindingBase);
}
}
...
}
In InternalTextProperty's propertyChangeCallback you can have the same code as OnBindingTextChanged() or you could refactor them into another method.
For those who don't want to write their own code. I have created a lightweight nuget package that adds highlighting to regular TextBlock. You can leave most of your code intact and only have to add a few attached properties. By default the add-on supports highlighting, but you can also enable bold, italic and underline on the keyword.
https://www.nuget.org/packages/HighlightableTextBlock/
The source is here: https://github.com/kthsu/HighlightableTextBlock

Counting font size asynchronously

I use WPF. My app has some textblocks with changeable text inside. Each one has width of 200 and height of 150. The problem is that I have 7 textblocks like those and I want them to have the same font size. The text must be autofitted. I know that can autofit them up. But when one has a sentence inside and another only two words, the font size is so different... I need to recount size asynchronically (e.g. creating some event like OnTextChange). Text inside blocks changes dynamically. How to write a function? I want to pass 3 parameters: text, font (font family + font style) and textblock size, and return fitted font size.
The best way to determine the appropriate font size is to measure the text at any arbitrary size, and then multiply it by the ratio of its size to the size of the area.
For example, if you measure the text and it is half of the size of the container it's in, you can multiply it by 2 and it should fill the container. You want to choose the minimum of either the width or height ratio to use.
In WPF the FormattedText class does text measuring.
public double GetFontSize(string text, Size availableSize, Typeface typeFace)
{
FormattedText formtxt = new FormattedText(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeFace, 10, Brushes.Black);
double ratio = Math.Min(availableSize.Width / formtxt.Width, availableSize.Height / formtxt.Height);
return 10 * ratio;
}
You would use this function whenever you change the text of your TextBlocks, like so:
txtBlock.FontSize = GetFontSize(txt.Text, new Size(txt.ActualWidth, txt.ActualHeight), new Typeface(txt.FontFamily, txt.FontStyle, txt.FontWeight, txt.FontStretch));
Edit:
For the purposes of practicality, you might want to be able to have the text to center vertically in this predefined bounding rectangle. A good way to do that is to wrap your TextBlock inside another object such as a Border element. This way you can tell your TextBlock to align in the center of the border and it can auto-size to fit its content.
Ok. Works fine. But I've got another problem. I wrote 2 methods in MainWindow.cs:
private void fitFontSize()
{
propertiesList.Clear();
TextUtils.FontProperty fontProps = new TextUtils.FontProperty();
foreach (TextBlock tb in findVisualChildren<TextBlock>(statusOptionsGrid))
{
fontProps.Text = tb.Text;
fontProps.Size = new Size(tb.ActualWidth, tb.ActualHeight);
fontProps.FontFamily = tb.FontFamily;
fontProps.FontStyle = tb.FontStyle;
fontProps.FontWeight = tb.FontWeight;
fontProps.FontStretch = tb.FontStretch;
propertiesList.Add(fontProps);
}
MessageBox.Show(TextUtils.recalculateFontSize(propertiesList) + "");
}
public IEnumerable<T> findVisualChildren<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)
{
yield return (T)child;
}
foreach (T childOfChild in findVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
After that I created a new class for text processing. Here's the code:
public class TextUtils
{
public class FontProperty
{
public FontFamily FontFamily { get; set; }
public FontStyle FontStyle { get; set; }
public FontWeight FontWeight { get; set; }
public FontStretch FontStretch { get; set; }
public string Text { get; set; }
public Size Size { get; set; }
public Typeface getTypeFace()
{
return new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
}
}
public static double recalculateFontSize(List<FontProperty> propertiesList)
{
List<double> fontSizes = new List<double>();
foreach (FontProperty fp in propertiesList)
{
fontSizes.Add(getFontSizeForControl(fp));
}
return fontSizes.Min<double>();
}
private static double getFontSizeForControl(FontProperty fp)
{
string text = fp.Text;
Size availableSize = fp.Size;
Typeface typeFace = fp.getTypeFace();
FormattedText formtxt = new FormattedText(text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, typeFace, 10, Brushes.Black);
double ratio = Math.Min(availableSize.Width / formtxt.Width, availableSize.Height / formtxt.Height);
return 10 * ratio;
}
}
The code looks bad but I will correct it later...
Ok. Now I want to create a new timer which checks font sizes every second. When I try to use fitFontSize() method in it, I get the msg: "The calling thread cannot access this object because a different thread owns it."
How to do this in order to avoid problems like that?
I tried (just a try) creating new thread calling this method. But there is the same problem with findVisualChildren<>() method - it's called in fitFontSize(). I don't have any ideas how to solve my problem...

Creating SelectionBorder: Bit in the face by decimal rounding?

I am currently implementing a class called SelectionBorder in WPF. It's derived from the Shape class.
It basically looks like this:
public class SelectionBorder : Shape
{
public Point StartPoint {get; set;}
public PointCollection Points {get; set;}
public double StrokeLength {get; set;}
protected override Geometry DefiningGeometry{
get{
//Magic!
}
}
}
The StartPoint and Points properties determine the corners of the border. The border is a typical stroked line border (one black stroke, one invisible stroke like that: - - - -)
The problem that I have now is that since the corner points are freely choosable it's pretty common that the count of strokes (meaning black and invisible strokes) is not even (in fact not even an integer) and therefore the first stroke looks longer than the others (visible in the picture). This maybe doesn't seem to be a big deal but I later want to animate the border so that the strokes circle round the content. When doing this animation the tiny flaw in the static view becomes clearly visible and in my opinion is highly disturbing.
alt text http://img14.imageshack.us/img14/2874/selectionborder.png
The problem is that I tried to determine a StrokeLength that gets as close to the original StrokeLength as possible and creates an even number of strokes. However the problem I've run into is that WPF (obviously) can't display the whole precision of a double decimal StrokeLength and therefore the resulting stroke number is uneven once again.
Is there any workaround for this problem? Do you probably have another solution for my problem?
Thanks in advance!
EDIT: I retested and reviewed the code after a little break for fitness today and after all it happens only on very big StrokeLengths. I plan to use StrokeLengths of 2 where the little animation jumping does matter much less than I originally thought.
You could make more than one corner "un-matched" in that regard. For example, instead of having one point be the "source" and "destination" of the animated dashes, you could pick 2 points. One would be the "source", dashes appearing to march away from it in 2 directions, and another point be the "destination", where dashes converge and disappear.
GIMP, for example, animates selection dashed lines in this way and seems to pick a point closest to the lower-left for the "source" and a point closest to the upper-right for the "destination".
You could come up with some other scheme, as well.
Just remember that while it may look disturbing to you, most users will not care.
I just found a way that makes it way easier to create such an animated SelectionBorder.
Instead of creating the animation by moving an self-created AnimationPoint through animation I just animated the StrokeDashOffset property natively provided by the Shape class and setting the StrokeDashArray to define the StrokeLength.
It would look like this in XAML:
<namespace:SelectionBorder StrokeDashArray="2" AnimationDuration="0:0:1" Stroke="Black" />
The class looks like this:
public class SelectionBorder : Shape
{
private DoubleAnimation m_Animation;
private bool m_AnimationStarted;
public SelectionBorder()
{
IsVisibleChanged += OnIsVisibleChanged;
}
protected void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (Visibility == Visibility.Visible)
{
StartAnimation();
}
else
{
StopAnimation();
}
}
public void StartAnimation()
{
if (m_AnimationStarted)
return;
if (m_Animation == null)
{
m_Animation = CreateAnimation();
}
BeginAnimation(StrokeDashOffsetProperty, m_Animation);
m_AnimationStarted = true;
}
protected virtual DoubleAnimation CreateAnimation()
{
DoubleAnimation animation = new DoubleAnimation();
animation.From = 0;
if (StrokeDashArray.Count == 0)
animation.To = 4;
else
animation.To = StrokeDashArray.First() * 2;
animation.Duration = AnimationDuration;
animation.RepeatBehavior = RepeatBehavior.Forever;
return animation;
}
public void StopAnimation()
{
if (m_AnimationStarted)
{
BeginAnimation(StrokeDashOffsetProperty, null);
m_AnimationStarted = false;
}
}
#region Dependency Properties
public Duration AnimationDuration
{
get { return (Duration)GetValue(AnimationDurationProperty); }
set { SetValue(AnimationDurationProperty, value); }
}
public static readonly DependencyProperty AnimationDurationProperty =
DependencyProperty.Register("AnimationDuration", typeof(Duration), typeof(SelectionBorder), new UIPropertyMetadata(new Duration(TimeSpan.FromSeconds(0.5))));
#endregion Dependency Properties
protected override Geometry DefiningGeometry
{
get
{
double width = (double.IsNaN(Width)) ? ((Panel)Parent).ActualWidth : Width;
double height = (double.IsNaN(Height)) ? ((Panel)Parent).ActualHeight : Height;
RectangleGeometry geometry = new RectangleGeometry(new Rect(0, 0, width, height));
return geometry;
}
}
}

WPF doesn't honor Textbox.MinLines for Auto height calculation

I want to have a TextBox which Height grows as Iam entering lines of Text.
I've set the Height property to "Auto", and so far the growing works.
Now I want that the TextBox's Height should be at least 5 lines.
Now I've set the MinLines property to "5" but if I start the app the TextBox's height is still one line.
Try setting the MinHeight property.
A hack to make the MinLines property work
public class TextBoxAdv : TextBox
{
bool loaded = false;
/// <summary>
/// Constructor
/// </summary>
public TextBoxAdv()
{
Loaded += new RoutedEventHandler( Control_Loaded );
SetResourceReference( StyleProperty, typeof( TextBox ) );
}
void Control_Loaded( object sender, RoutedEventArgs e )
{
if( !loaded )
{
loaded = true;
string text = Text;
Text = "Text";
UpdateLayout();
Text = text;
}
}
}
I propose a different solution that properly respects the MinLines property, rather than forcing you to use MinHeight.
First, start with a convenience method to allow you to Post an action to the window loop. (I'm including both one where you need to pass state and one where you don't.)
public static class Globals {
public static void Post(Action callback){
if(SynchronizationContext.Current is SynchronizationContext currentContext)
currentContext.Post( _ => callback(), null);
else{
callback();
}
}
public static void Post<TState>(TState state, Action<TState> callback){
if(SynchronizationContext.Current is SynchronizationContext currentContext)
currentContext.Post(_ => callback(state), null);
else{
callback(state);
}
}
}
Next, create an extension method for TextBox to 'initialize' the proper size based on MinLines. I put this in a Hacks class because to me, that's what this is and it clearly identifies the code as such.
public static void FixInitialMinLines(this TextBox textBox) {
Globals.Post(() => {
var textBinding = textBox.GetBindingExpression(TextBox.TextProperty)?.ParentBinding;
if (textBinding != null) {
BindingOperations.ClearBinding(textBox, TextBox.TextProperty);
textBox.UpdateLayout();
BindingOperations.SetBinding(textBox, TextBox.TextProperty, textBinding);
}
else {
var lastValue = textBox.Text;
textBox.Text = lastValue + "a";
textBox.UpdateLayout();
textBox.Text = lastValue;
}
});
}
The above code handles both bound and unbound TextBox controls, but rather than simply changing the value like other controls which may cascade that change down through the bindings, it first disconnects the binding, forces layout, then reconnects the binding, thus triggering the proper layout in the UI. This avoids unintentionally changing your bound sources should the binding be two-way.
Finally, simply call the extension method for every TextBox where MinLines is set. Thanks to the Post call in the extension method, You can call this immediately after InitializeComponent and it will still be executed after all other events have fired, including all layout and the Loaded event.
public partial class Main : Window {
public Main() {
InitializeComponent();
// Fix initial MinLines issue
SomeTextBoxWithMinLines.FixInitialMinLines();
}
...
}
Add the above code to your 'library' of functions and you can address the issue with a single line of code in all of your windows and controls. Enjoy!

Resources