Whole line highlighting in AvalonEdit - c#

I'm creating a view for patch files using AvalonEdit, and I want to make it so that diffs are highlighted across the entire line, not just the text background - similar to what GitHub for Windows does today:
I'm new to AvalonEdit, so I'm not sure the best way to do this. Here's what I've found so far:
Override VisualLineElementGenerator in order to create an additional TextSpan that is the length of the control. This seems Tricky.
Create a new control to add to TextView.Layers in the background and OnRender in the green/red by hand - this seems more promising, but it's not super clear what event I need to hook in order to detect when to re-render.
Override TextView - this seems like overkill.
Edit: Here's what happens with a simple syntax highlighter, which is what I don't want:

As Daniel mentioned, I discovered Background Renderers via the Wiki page and it worked great. Here's what I ended up doing - it can probably be made a bit more efficient but it's good enough for now:
public class DiffLineBackgroundRenderer : IBackgroundRenderer
{
static Pen pen;
static SolidColorBrush removedBackground;
static SolidColorBrush addedBackground;
static SolidColorBrush headerBackground;
FileDiffView host;
static DiffLineBackgroundRenderer()
{
removedBackground = new SolidColorBrush(Color.FromRgb(0xff, 0xdd, 0xdd)); removedBackground.Freeze();
addedBackground = new SolidColorBrush(Color.FromRgb(0xdd, 0xff, 0xdd)); addedBackground.Freeze();
headerBackground = new SolidColorBrush(Color.FromRgb(0xf8, 0xf8, 0xff)); headerBackground.Freeze();
var blackBrush = new SolidColorBrush(Color.FromRgb(0, 0, 0)); blackBrush.Freeze();
pen = new Pen(blackBrush, 0.0);
}
public DiffLineBackgroundRenderer(FileDiffView host)
{
this.host = host;
}
public KnownLayer Layer
{
get { return KnownLayer.Background; }
}
public void Draw(TextView textView, DrawingContext drawingContext)
{
foreach (var v in textView.VisualLines)
{
var rc = BackgroundGeometryBuilder.GetRectsFromVisualSegment(textView, v, 0, 1000).First();
// NB: This lookup to fetch the doc line number isn't great, we could
// probably do it once then just increment.
var linenum = v.FirstDocumentLine.LineNumber - 1;
if (linenum >= host.ViewModel.Lines.Count) continue;
var diffLine = host.ViewModel.Lines[linenum];
if (diffLine.Style == DiffLineStyle.Context) continue;
var brush = default(Brush);
switch (diffLine.Style)
{
case DiffLineStyle.Header:
brush = headerBackground;
break;
case DiffLineStyle.Added:
brush = addedBackground;
break;
case DiffLineStyle.Deleted:
brush = removedBackground;
break;
}
drawingContext.DrawRectangle(brush, pen,
new Rect(0, rc.Top, textView.ActualWidth, rc.Height));
}
}
}

Create a new control to add to TextView.Layers in the background and OnRender in the green/red by hand
You don't have to create a new layer to render in something in the background: you can add your implementation of IBackgroundRenderer to textView.BackgroundRenderers to render something in the background of an existing layer.
it's not super clear what event I need to hook in order to detect when to re-render
That would be: textView.VisualLinesChanged. But you don't need that if you use IBackgroundRenderer as the existing layers are already re-rendered by AvalonEdit.

Related

How to update wireframe without WPF button click?

I'm developing a Windows Form application with WPF User Control embedded in the WF. If I add a button and execute my userControl.DrawWireFrameCube(); My ViewPort3D get updated. I'm using Helix 3D Toolkit. But If I call my method from my MainWindowForm class it doesn't get executed and UI is not updated,but only userControl.DrawWireFrameCube(); isn't working. The other userControl.Draw3DObject(insertionPoint, points, color); method is working fine.
private void VisualizePart(int index)
{
InsertionPoint insertionPoint = new InsertionPoint
{
X = _duplicatedListParts[index].INFO1,
Y = _duplicatedListParts[index].INFO2,
Z = _duplicatedListParts[index].INFO3
};
DetailSize points = new DetailSize
{
Length = _duplicatedListParts[index].LENGTH,
Width = _duplicatedListParts[index].WIDTH,
Thickness = _duplicatedListParts[index].THICKNESS
};
userControl.Dispatcher.Invoke(() =>
{
System.Windows.Media.Color color = System.Windows.Media.Color.FromRgb(255, 90, 0);
userControl.Draw3DObject(insertionPoint, points, color);
userControl.DrawWireFrameCube();
});
}
The difference is that in my Draw3DObject() I add items to Model3DGroup and in DrawWireFrameCube() I add items to MyViewPort3D. I'm not using XAML and I want to stay that way.
Any ideas what is wrong here?
P.S I love negative vote without explanation :)

Creating .NET equivalent Colors static class in Mono Android (Xamarin.Android)

A lot of Canvas methods in the Android API require a Paint object to be defined in order to define a color. The method for doing this is,
Paint myPaintObject = new Paint();
myPaintObject.Color = Color.Red;
canvas.DrawRect(..., myPaintObject);
it would be better if it looked like this,
canvas.DrawRect(..., Colors.Red);
A solution class might look like this...
public static class Colors
{
public static Paint Red { get { return GetColors(Color.Red); } }
public static Paint Black { get { return GetColors(Color.Black); } }
private static Paint GetColors(Color color)
{
Paint paint = new Paint ();
paint.Color = color;
return paint;
}
}
but it would suck to have to create getters for every color available. Any ideas for making this easier?
edit: LINQ is a pretty good solution for this. Per #ChrisSinclair's comment regarding populate List with SolidColorBrush brushes
this.Colors = typeof(Color)
.GetProperties(System.Reflection.BindingFlags.Static |
System.Reflection.BindingFlags.Public)
.ToDictionary(p => p.Name,
p => new Paint()
{ Color = ((Color)p.GetValue(null, null)) });
When called, looks like,
canvas.DrawRect(..., Colors["Red"]);
I would just recommend an extension method to convert from Color to Paint:
public static Paint AsPaint(this Color color)
{
Paint paint = new Paint ();
paint.Color = color;
return paint;
}
This would allow you to write, for any color:
canvas.DrawRect(..., Color.Red.AsPaint());
One advantage here is that you're not hiding the fact that you're creating a Paint instance each time. Using Colors.Red suggests that you're creating a Color, not a Paint, and masks that it's being constructed with each call.
Otherwise, if you wish to make a Colors class with each property, you'll need a property per Color you with to support. This could be done by scripting the creation of the source file, but there is no direct way to create all of these "colors" without writing a property per color.

Find the height of text in a panel

I have panel that I have customized. I use it to display text. But sometimes that text is too long and wraps to the next line. Is there some way I can auto resize the panel to show all the text?
I am using C# and Visual Studio 2008 and the compact framework.
Here is the code I am wanting to adjust the size for:
(Note: HintBox is my own class that inherits from panel. So I can modify it as needed.)
public void DataItemClicked(ShipmentData shipmentData)
{
// Setup the HintBox
if (_dataItemHintBox == null)
_dataItemHintBox = HintBox.GetHintBox(ShipmentForm.AsAnObjectThatCanOwn(),
_dataShipSelectedPoint,
new Size(135, 50), shipmentData.LongDesc,
Color.LightSteelBlue);
_dataItemHintBox.Location = new Point(_dataShipSelectedPoint.X - 100,
_dataShipSelectedPoint.Y - 50);
_dataItemHintBox.MessageText = shipmentData.LongDesc;
// It would be nice to set the size right here
_dataItemHintBox.Size = _dataItemHintBox.MethodToResizeTheHeightToShowTheWholeString()
_dataItemHintBox.Show();
}
I am going to give the answer to Will Marcouiller because his code example was the closest to what I needed (and looks like it will work). However, this is what I think I will use:
public static class CFMeasureString
{
private struct Rect
{
public readonly int Left, Top, Right, Bottom;
public Rect(Rectangle r)
{
this.Left = r.Left;
this.Top = r.Top;
this.Bottom = r.Bottom;
this.Right = r.Right;
}
}
[DllImport("coredll.dll")]
static extern int DrawText(IntPtr hdc, string lpStr, int nCount,
ref Rect lpRect, int wFormat);
private const int DT_CALCRECT = 0x00000400;
private const int DT_WORDBREAK = 0x00000010;
private const int DT_EDITCONTROL = 0x00002000;
static public Size MeasureString(this Graphics gr, string text, Rectangle rect,
bool textboxControl)
{
Rect bounds = new Rect(rect);
IntPtr hdc = gr.GetHdc();
int flags = DT_CALCRECT | DT_WORDBREAK;
if (textboxControl) flags |= DT_EDITCONTROL;
DrawText(hdc, text, text.Length, ref bounds, flags);
gr.ReleaseHdc(hdc);
return new Size(bounds.Right - bounds.Left, bounds.Bottom - bounds.Top +
(textboxControl ? 6 : 0));
}
}
This uses the os level call to draw text. By P-Invoking it I can get the functionality I need (multi line wrapping). Note that this method does not include any margins. Just the actual space taken up by the text.
I did not write this code. I got it from http://www.mobilepractices.com/2007/12/multi-line-graphicsmeasurestring.html. That blog post had my exact problem and this fix. (Though I did make a minor tweak to make it a extension method.)
You could use the Graphics.MeasureString() method.
With a code sample of your text assignment onto your panel, I could perhaps provide a code sample using the MeasureString() method, if you need it.
I have no way to know whether the Graphics.MeasureString() method is part of the Compact Framework, as it is not said on the page I linked.
EDIT #1
Here's a link where I answered to another text-size related question, while I look for writing a sample for you. =)
EDIT #2
Here's another link related to your question. (Next edit is the sample code. =P)
EDIT #3
public void DataItemClicked(ShipmentData shipmentData) {
// Setup the HintBox
if (_dataItemHintBox == null)
_dataItemHintBox = HintBox.GetHintBox(ShipmentForm.AsAnObjectThatCanOwn(),
_dataShipSelectedPoint,
new Size(135, 50), shipmentData.LongDesc,
Color.LightSteelBlue);
// Beginning to measure the size of the string shipmentData.LongDesc here.
// Assuming that the initial font size should be 30pt.
Single fontSize = 30.0F;
Font f = new Font("fontFamily", fontSize, FontStyle.Regular);
// The Panel.CreateGraphics method provides the instance of Graphics object
// that shall be used to compare the string size against.
using (Graphics g = _dataItemHintBox.CreateGraphics()) {
while (g.MeasureString(shipmentData.LongDesc, f).Width > _dataItemHintBox.Size.Width - 5) {
--fontSize;
f = new Font("fontFamily", fontSize, FontStyle.Regular);
}
}
// Font property inherited from Panel control.
_dataItemHintBox.Font = f;
// End of font resizing to fit the HintBox panel.
_dataItemHintBox.Location = new Point(_dataShipSelectedPoint.X - 100,
_dataShipSelectedPoint.Y - 50);
_dataItemHintBox.MessageText = shipmentData.LongDesc;
// It would be nice to set the size right here
_dataItemHintBox.Size = _dataItemHintBox.MethodToResizeTheHeightToShowTheWholeString()
_dataItemHintBox.Show();
}
Disclaimer: This code has not been tested and is off the top of my head. Some changes might be obligatory in order for you to test it. This provides a guideline to achieve what you seem to want to accomplish. There might be a better way to do this, but I know this one works. Well, the algorithm works, as you can see in my other answers.
Instead of the line:
SizeF fontSize = 30.0F;
You could as well do the following:
var fontSize = _dataItemHintBox.Font.Size;
Why is this?
Because Font.Size property is readonly. So, you need to create a new instance of the System.Drawing.Font class each time the Font.Size shall change.
In your comparison, instead of having the line:
while (g.MeasureString(shipmentData.LongDesc, f)...)
you could also have:
while (g.MeasureString(shipmentData.LongDesc, _dataItemHintBox.Font)...)
This would nullify the need for a second Font class instance, that is f.
Please feel free to post feedbacks as I could change my sample to fit your reality upon the feedbacks received, so that it better helps you. =)
I hope this helps! =)
You can use whichever of the TextRenderer.MeasureText overloads is appropriate for you. Using this function, you can determine the actual rendered size of a string and adjust your panel's size accordingly.
If you're trying to measure inside the Paint event, then you could use the MeasureString function on your e.Graphics object, but resizing inside Paint is not wise. Using TextRenderer avoids your having to create a Graphics object with CreateGraphics() and disposing of it when you're finished.
EDIT
Since TextRenderer is not supported on the compact framework (I missed the tag the first time I saw the question), you'll have to use MeasureString() function on the Graphics object. Something like this:
public Size GetStringSize(string text)
{
using(Graphics g = yourPanel.CreateGraphics())
{
return g.MeasureString(text, yourPanel.Font);
}
}

Win32 doesn't exist - how can I declare or reference it?

I'm attempting to use code I've found that uses Win32. However, I'm getting this error:
The Name 'Win32' does not exist in the current context.
What am I missing? How do you call/declare Win32?
public class TransparentTextBox : TextBox
{
PictureBox pictureBox = new PictureBox();
public TransparentTextBox()
{
pictureBox.Dock = DockStyle.Fill;
this.Controls.Add(pictureBox);
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
switch (m.Msg)
{
case Win32.WM_PAINT:
Bitmap bmpCaptured =
new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
Bitmap bmpResult =
new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
Rectangle r =
new Rectangle(0, 0, this.ClientRectangle.Width,
this.ClientRectangle.Height);
CaptureWindow(this, ref bmpCaptured);
this.SetStyle(ControlStyles.SupportsTransparentBackColor, true);
this.BackColor = Color.Transparent;
ImageAttributes imgAttrib = new ImageAttributes();
ColorMap[] colorMap = new ColorMap[1];
colorMap[0] = new ColorMap();
colorMap[0].OldColor = Color.White;
colorMap[0].NewColor = Color.Transparent;
imgAttrib.SetRemapTable(colorMap);
Graphics g = Graphics.FromImage(bmpResult);
g.DrawImage(bmpCaptured, r, 0, 0, this.ClientRectangle.Width,
this.ClientRectangle.Height, GraphicsUnit.Pixel, imgAttrib);
g.Dispose();
pictureBox.Image = (Image)bmpResult.Clone();
break;
case Win32.WM_HSCROLL:
case Win32.WM_VSCROLL:
this.Invalidate(); // repaint
// if you use scrolling then add these two case statements
break;
}
}
Those are not defined in the .NET framework. Whoever wrote this code had them defined in another class. They are from the Win32 API which is why they are named Win32. You'll need to search on each of those defines and find out what they should be.
I can tell you that the Win32.WH* are window messages they are just integer values.
Edit: pinvoke.net has a full list of window messages.
I suspect you have grabbed code that relies on this Win32 helper class.
Adding that to your project should solve the immediate Win32 missing problem.
Yeah, do what David said which you said you already did (pInvoke). Then look at the source you found and look where Win32.WM_PAINT, Win32.WM_HSCROLL, and Win32.WM_VSCROLL are defined. In my experience, these are just defined by hand in the source and are just numbers, to make it easier to read. Of course these numbers correspond to what they're actually defined in the Win32 API, but they're redefined in the source by the programmer to avoid having to use the numbers directly, therefore making it harder to read.
On the other hand, if you want a transparent textbox, why not just reference PresentationCore/PresentationFramework/WindowsFormsIntegration and use this to create a transparent text box:
using WPFTextBox = System.Windows.Controls.TextBox;
using WPFBrushes = System.Windows.Media.Brushes;
using WPFElementHost = System.Windows.Forms.Integration;
...
var textBox = new WPFTextBox { Background = WPFBrushes.Transparent };
var host = new WPFElementHost { Dock = DockStyle.Fill, Child = textBox };
then add the host to your container and you're set to go. A lot easier than messing with a bunch of Win32 complexity, and easy to wrap in a custom class.
Why beat your head against WinForms when the functionality is trivial to achieve in WPF? Windows 98, ME, and 2000 now account for about 1% of the market in total. (WPF won't run on any Windows OS below XP)

TreeNode Image Overlay

I have an imagelist of about 30 images, and 3 images I'd like to be able to overlay on top of the 30 when a TreeNode is in a particular state. I know that a C++ TreeItem can do this with the TVIS_OVERLAYMASK as such:
SetItemState(hItem,INDEXTOOVERLAYMASK(nOverlayIndex), TVIS_OVERLAYMASK);
Is there any mechanism to achieve similar results in .NET?
I see this question is still getting views, so I'll post the implementation of what David suggested.
internal class MyTree : TreeView
{
internal MyTree() :
base()
{
// let the tree know that we're going to be doing some owner drawing
this.DrawMode = TreeViewDrawMode.OwnerDrawText;
this.DrawNode += new DrawTreeNodeEventHandler(MyTree_DrawNode);
}
void MyTree_DrawNode(object sender, DrawTreeNodeEventArgs e)
{
// Do your own logic to determine what overlay image you want to use
Image overlayImage = GetOverlayImage();
// you have to move the X value left a bit,
// otherwise it will draw over your node text
// I'm also adjusting to move the overlay down a bit
e.Graphics.DrawImage(overlayImage,
e.Node.Bounds.X - 15, e.Node.Bounds.Y + 4);
// We're done! Draw the rest of the node normally
e.DefaultDraw = true
}
}
Why don't you just generate the image with the overlay on demand even, so you don't have to waste precious CPU cycles like this:
http://madprops.org/blog/highlighting-treenodes-with-an-overlay-image/ :
private void InitializeLinkedTreeImages()
{
foreach (string key in treeImages.Images.Keys)
{
Bitmap bmp = new Bitmap(treeImages.Images[key]);
Graphics g = Graphics.FromImage(bmp);
g.DrawImageUnscaled(Properties.Resources.Linked16, 0, 0);
treeImages.Images.Add(key + "Linked", bmp);
}
}
I don't know of a way to do the overlay automatically, but you could do this with an owner drawn tree node.

Categories

Resources