Finding width of characters to do wordwrap - wpf

I'm writing a custom texteditor using WPF and am using a stack of TextBlocks to render the lines. That leads me to the problem to find when to wordwrap from one line to the next.
What is the best way to do this? Is there a way to find the width of each characther/glyph (other than creating a FormatedText class for each character)?

I had to do this in order to solve this problem. Maybe you could check out the code there. The relevant part of the code is:
var textPointer = run.ContentStart;
textPointer = textPointer.GetPositionAtOffset(start, LogicalDirection.Forward);
var leftRectangle = textPointer.GetCharacterRect(LogicalDirection.Forward);
textPointer = textPointer.GetPositionAtOffset(length, LogicalDirection.Forward);
var rightRectangle = textPointer.GetCharacterRect(LogicalDirection.Backward);
var rect = new Rect(leftRectangle.TopLeft, rightRectangle.BottomRight);
var translatedPoint = uiHost.TranslatePoint(new Point(0, 0), null);
rect.Offset(translatedPoint.X, translatedPoint.Y);

Related

Hide some points in Oxyplot Line series

I need to show/hide some data points in oxyplot line series. Is it possible?
Though some markers are invisible, the line should go through the invisible markers.
You could make use of two series to achieve this. The first one would draw the complete points (and line) without the marker. The second series would draw the visible points(with marker,but with line style set to none). For Example
DataPoint[] points = new DataPoint[]
{
new DataPoint(1,12),
new DataPoint(2,10),
new DataPoint(3,9),
new DataPoint(4,13),
new DataPoint(5,14),
new DataPoint(6,10)
};
var seriesComplete = new OxyPlot.Series.LineSeries();
seriesComplete.Points.AddRange(points);
var seriesVisible = new OxyPlot.Series.LineSeries();
seriesVisible.Points.AddRange(points.Where(x => x.Y % 2 == 0));
seriesVisible.MarkerFill = OxyColors.Blue;
seriesVisible.MarkerType = MarkerType.Circle;
seriesVisible.MarkerSize = 10;
seriesVisible.LineStyle = LineStyle.None;
this.MyModel.Series.Add(seriesComplete);
this.MyModel.Series.Add(seriesVisible);
Result is attached as image

Geometry.Combine doesn't work for curves

I want to combine 2 curves like this:
Then here is my code:
// Create a path to draw a geometry with.
Path myPath = new Path();
myPath.Stroke = Brushes.Black;
myPath.StrokeThickness = 1;
var gmy1 = (StreamGeometry)StreamGeometry.Parse("M100,100C110,118.333333333333 138.333333333333,206.666666666667 160,210 181.666666666667,213.333333333333 205,123.333333333333 230,120 255,116.666666666667 280,186.666666666667 310,190 340,193.333333333333 396.666666666667,156.666666666667 410,140 423.333333333333,123.333333333333 393.333333333333,98.3333333333333 390,90");
var gmy2 = (StreamGeometry)StreamGeometry.Parse("M180,241.25L180,241.25 230,290 300,246.66667175293 330,160");
var gmy = Geometry.Combine(gmy1, gmy2, GeometryCombineMode.Union, null);
myPath.Data = gmy;
// Add path shape to the UI.
this.panel1.Children.Add(myPath);
But the result is this:
How to combine the curves in WPF?
And because of the project limitation, we have to implement this without layout and xaml. That means we need the result type is Geometry.
More general than concatenating path strings:
If you have a set of arbitrary Geometries and want to group them, use a GeometryGroup:
Geometry gmy1 = ...;
Geometry gmy2 = ...;
var gmy = new GeometryGroup();
gmy.Children.Add(gmy1);
gmy.Children.Add(gmy2);
myPath.Data = gmy;
Easy:
Path myPath = new Path();
myPath.Stroke = Brushes.Black;
myPath.StrokeThickness = 1;
var gmy1 = (StreamGeometry)StreamGeometry.Parse("M100,100C110,118.333333333333 138.333333333333,206.666666666667 160,210 181.666666666667,213.333333333333 205,123.333333333333 230,120 255,116.666666666667 280,186.666666666667 310,190 340,193.333333333333 396.666666666667,156.666666666667 410,140 423.333333333333,123.333333333333 393.333333333333,98.3333333333333 390,90");
var gmy2 = (StreamGeometry)StreamGeometry.Parse("M180,241.25L180,241.25 230,290 300,246.66667175293 330,160");
var gmy = (StreamGeometry)StreamGeometry.Parse(gmy1.ToString() + gmy2.ToString());
myPath.Data = gmy;
// Add path shape to the UI.
this.panel1.Children.Add(myPath);
The path definition language is a language. Use it as one. StreamGeometry.ToString() unparses a Geometry back to its Path Definition Language representation, which you can then merge with another one.
Note that this works because each starts with a M for Move command: It starts a new line. I don't think there's any realistic case where you'd run into any trouble with that (and it won't let you start with L for Line), but theory's not exactly my strongest subject.
Just add both of them to a Grid or Canvas, Combine does a intersecting combination, you just seem to want to overlay them. Alternatively add both of them to a GeometryGroup and add that to your panel.

WPF, Canvas, FormattedText.BuildGeometry - why is nothing displayed?

I have a need to display text mixed with geometries - and so far I have the geometries displayed nicely (removed from example below), but the text doesn't show up at all.
I've found the example for formatted text below and think I should be able to take the buildGeometry and enclose it in a path to be shown in a canvas.
Can anybody help me see what I am doing wrong?
Thanks for any responses,
Anders, Denmark
var canvas = new Canvas();
var formattedText = new FormattedText(
"Hello world",
CultureInfo.GetCultureInfo("en-us"),
FlowDirection.LeftToRight,
new Typeface("Verdana"),
32,
Brushes.Black);
var buildGeometry = formattedText.BuildGeometry(new Point(500, 500));
var path = new System.Windows.Shapes.Path();
path.Data = buildGeometry;
canvas.Children.Add(path);
Content = canvas;
Ay caramba,
A Path needs a Stroke defined in order to show anything(!).
Apologies to all spending time on my question....
var path = new System.Windows.Shapes.Path
{
Stroke = Brushes.Black,
StrokeThickness = 1
};

Printing a WPF FlowDocument

I'm building a demo app in WPF, which is new to me. I'm currently displaying text in a FlowDocument, and need to print it.
The code I'm using looks like this:
PrintDialog pd = new PrintDialog();
fd.PageHeight = pd.PrintableAreaHeight;
fd.PageWidth = pd.PrintableAreaWidth;
fd.PagePadding = new Thickness(50);
fd.ColumnGap = 0;
fd.ColumnWidth = pd.PrintableAreaWidth;
IDocumentPaginatorSource dps = fd;
pd.PrintDocument(dps.DocumentPaginator, "flow doc");
fd is my FlowDocument, and for now I'm using the default printer instead of allowing the user to specify print options. It works OK, except that after the document prints, the FlowDocument displayed on screen has changed to to use the settings I specified for printing.
I can fix this by manually resetting everything after I print, but is this the best way? Should I make a copy of the FlowDocument before I print it? Or is there another approach I should consider?
yes, make a copy of the FlowDocument before printing it. This is because the pagination and margins will be different. This works for me.
private void DoThePrint(System.Windows.Documents.FlowDocument document)
{
// Clone the source document's content into a new FlowDocument.
// This is because the pagination for the printer needs to be
// done differently than the pagination for the displayed page.
// We print the copy, rather that the original FlowDocument.
System.IO.MemoryStream s = new System.IO.MemoryStream();
TextRange source = new TextRange(document.ContentStart, document.ContentEnd);
source.Save(s, DataFormats.Xaml);
FlowDocument copy = new FlowDocument();
TextRange dest = new TextRange(copy.ContentStart, copy.ContentEnd);
dest.Load(s, DataFormats.Xaml);
// Create a XpsDocumentWriter object, implicitly opening a Windows common print dialog,
// and allowing the user to select a printer.
// get information about the dimensions of the seleted printer+media.
System.Printing.PrintDocumentImageableArea ia = null;
System.Windows.Xps.XpsDocumentWriter docWriter = System.Printing.PrintQueue.CreateXpsDocumentWriter(ref ia);
if (docWriter != null && ia != null)
{
DocumentPaginator paginator = ((IDocumentPaginatorSource)copy).DocumentPaginator;
// Change the PageSize and PagePadding for the document to match the CanvasSize for the printer device.
paginator.PageSize = new Size(ia.MediaSizeWidth, ia.MediaSizeHeight);
Thickness t = new Thickness(72); // copy.PagePadding;
copy.PagePadding = new Thickness(
Math.Max(ia.OriginWidth, t.Left),
Math.Max(ia.OriginHeight, t.Top),
Math.Max(ia.MediaSizeWidth - (ia.OriginWidth + ia.ExtentWidth), t.Right),
Math.Max(ia.MediaSizeHeight - (ia.OriginHeight + ia.ExtentHeight), t.Bottom));
copy.ColumnWidth = double.PositiveInfinity;
//copy.PageWidth = 528; // allow the page to be the natural with of the output device
// Send content to the printer.
docWriter.Write(paginator);
}
}
You can use the code from the URL below, it wraps the flow document in a fixed document and prints that, the big advantage is that you can use it to add margin, headers and footers.
https://web.archive.org/web/20150502085246/http://blogs.msdn.com:80/b/fyuan/archive/2007/03/10/convert-xaml-flow-document-to-xps-with-style-multiple-page-page-size-header-margin.aspx
The following works with both text and non-text visuals:
//Clone the source document
var str = XamlWriter.Save(FlowDoc);
var stringReader = new System.IO.StringReader(str);
var xmlReader = XmlReader.Create(stringReader);
var CloneDoc = XamlReader.Load(xmlReader) as FlowDocument;
//Now print using PrintDialog
var pd = new PrintDialog();
if (pd.ShowDialog().Value)
{
CloneDoc.PageHeight = pd.PrintableAreaHeight;
CloneDoc.PageWidth = pd.PrintableAreaWidth;
IDocumentPaginatorSource idocument = CloneDoc as IDocumentPaginatorSource;
pd.PrintDocument(idocument.DocumentPaginator, "Printing FlowDocument");
}
I am also generating a WPF report off a Flow document, but I am purposely using the flow document as a print preview screen. I there for want the margins to be the same. You can read about how I did this here.
In your scenario I'm thinking why not just make a copy of your settings, instead of the entire flow document. You can then re-apply the settings if you wish to return the document back to it's original state.

Using WPF Imaging classes - Getting image dimensions without reading the entire file

Link this post I want to be able to read an image files height and width without reading in the whole file into memory.
In the post Frank Krueger mentions there is a way of doing this with some WPF Imaging classes. Any idea on how to do this??
This should do it:
var bitmapFrame = BitmapFrame.Create(new Uri(#"C:\Documents and Settings\All Users\Documents\My Pictures\Sample Pictures\Winter.jpg"), BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
var width = bitmapFrame.PixelWidth;
var height = bitmapFrame.PixelHeight;
Following Sir Juice's recommendation, here is some alternative code that avoids locking the image file:
using (var stream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read))
{
var bitmapFrame = BitmapFrame.Create(stream, BitmapCreateOptions.DelayCreation, BitmapCacheOption.None);
var width = bitmapFrame.PixelWidth;
var height = bitmapFrame.PixelHeight;
}

Resources