Ostatnio przygotowując raport w technologii RDLC stanąłem przed koniecznością ustalenia wysokości pól tekstowych w zależności od typu oferty, tak by rekordów typu A było tylko cztery na stronie, typu B sześć, a C 10.
Z tej racji, że raporty rdlc mogą być renderowane w różny sposób, to nie generują zdarzeń pozwalający na bierząco korygować wysokość (albo też generować indeks czy spis treści). Stąd też wybrałem inne rozwiązanie: ustalenie właściwości "CanGrow" pola tekstowego na true oraz zmienianie liczby linii zawartych w odpowiedniej komórce tabeli źródłowej. Teraz pozostało opracować kod do wyliczania wysokości, aby się łatwiej testował, to najpierw zdefiniowałem pomocniczy interfejs:
public interface ITextMeasurer
{
int NoOfLines(string text);
}
A potem jego implementacja:
using System;
using System.Drawing;
namespace Utilities.Graphics
{
public class TextMeasurer : ITextMeasurer, IDisposable
{
private readonly SizeF layoutArea;
private readonly Font font;
private readonly System.Drawing.Graphics graphics;
private readonly float oneLineHeight;
public TextMeasurer(string fontName, float fontSize,
float maxWidthInCm, float maxHeightInCm,
byte totalHorizontalPadding) :
this(new Font(fontName, fontSize), maxWidthInCm, maxHeightInCm,
totalHorizontalPadding)
{}
public TextMeasurer(Font font, float maxWidthInCm,
float maxHeightInCm,
byte totalHorizontalPadding)
{
this.font = font;
Bitmap bmp = new Bitmap(1, 1);
graphics = System.Drawing.Graphics.FromImage(bmp);
float width = (float)(maxWidthInCm / 2.54) *
bmp.HorizontalResolution - totalHorizontalPadding;
float height = maxHeightInCm * bmp.VerticalResolution;
layoutArea = new SizeF(width, height);
oneLineHeight = GetTextSize("W").Height;
}
public SizeF GetTextSize(string text)
{
return graphics.MeasureString(text, font, layoutArea,
StringFormat.GenericTypographic);
}
public int NoOfLines(string text)
{
float textHeight = GetTextSize(text).Height;
return (int)(textHeight / oneLineHeight);
}
#region IDisposable Members
public void Dispose()
{
graphics.Dispose();
}
#endregion
}
}
Jak widać, w powyższym rozwiązaniu korzystam z metody MeasureString pomocniczego obiektu graphics. Tak więc pozostało wykorzystać wyliczenie wysokości tekstu i w razie potrzeby dodać pare linii:
using System.Text;
using Sgh.Utilities.Graphics;
namespace Utilities.Texts
{
public class Corrector
{
private readonly ITextMeasurer textMeasurer;
public Corrector(ITextMeasurer textMeasurer)
{
this.textMeasurer = textMeasurer;
}
public string CorrectHeight(string inputString, int outputLinesCount)
{
int currentNoOfLines = textMeasurer.NoOfLines(inputString);
if (currentNoOfLines == outputLinesCount)
{
return inputString;
}
return AddEmptyLinesToString(inputString,
outputLinesCount - currentNoOfLines);
}
private static string AddEmptyLinesToString(
string inputString, int noLinesToAdd)
{
StringBuilder builder = new StringBuilder();
builder.Append(inputString);
for (int i = 0; i < noLinesToAdd; i++)
{
builder.Append("\r\n ");
}
return builder.ToString();
}
}
}
Jednak okazało się, że czasami w danych źródłowych jest więcej linii tekstu niż może pomieścić w komórcę, tak więc oprócz dodawania pustych wierszy konieczne jest dodanie przycinanie nadmiarowych, tak więc zmodyfikowałem kod metody CurrentHeight:
public string CorrectHeight(string inputString, int outputLinesCount)
{
int currentNoOfLines = textMeasurer.NoOfLines(inputString);
if (currentNoOfLines > outputLinesCount)
{
LineRemover remover = new LineRemover(inputString,
outputLinesCount, textMeasurer);
inputString = remover.OutputString;
currentNoOfLines = remover.TextHeight;
}
if (currentNoOfLines == outputLinesCount)
{
return inputString;
}
return AddEmptyLinesToString(inputString,
outputLinesCount - currentNoOfLines);
}
Jak widać teraz wykorzystywany jest obiekt klasy LineRemover. Tnąc tekst wzdłuż spacji / znaków końca wiersza (za pomocą wyrażenia regularnego), wykorzystuje wyszukiwanie binarne do znaleznienia takiego miejsca, dla którego długość ciągu jest największa, a wysokość napisu jest nie wyższa od wymaganej.
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Utilities.Graphics;
namespace Utilities.Texts
{
public class LineRemover
{
private readonly string outputString;
private int textHeight;
public LineRemover(string inputString, int outputLinesCount,
ITextMeasurer textMeasurer)
{
this.outputString = RemoveLines(inputString,
outputLinesCount, textMeasurer);
}
public string OutputString
{
get { return this.outputString; }
}
public int TextHeight
{
get { return this.textHeight; }
}
private string RemoveLines(string inputString,
int outputLinesCount,
ITextMeasurer textMeasurer)
{
List<int> whitespaces = FindWhitespaces(inputString);
int lowIndex = 0;
int highIndex = whitespaces.Count;
while (highIndex > lowIndex + 1)
{
int currentIndex = (lowIndex + highIndex) / 2;
string currentText = inputString.Substring(0,
whitespaces[currentIndex]);
textHeight = textMeasurer.NoOfLines(currentText);
if (textHeight > outputLinesCount)
{
highIndex = currentIndex;
}
else
{
lowIndex = currentIndex;
}
}
return inputString.Substring(0, whitespaces[lowIndex]);
}
private List<int> FindWhitespaces(string inputString)
{
string pattern = @"\s+";
Regex regex = new Regex(pattern, RegexOptions.Compiled);
MatchCollection matches = regex.Matches(inputString);
List<int> whitespaces = new List<int>();
whitespaces.Add(0); // first char
foreach (Match match in matches)
{
whitespaces.Add(match.Index);
}
whitespaces.Add(inputString.Length);
return whitespaces;
}
}
}
Przykład wykorzystania:
float widthInCm = 8f;
using (TextMeasurer textMeasurer = new TextMeasurer(
"Arial", 8f, widthInCm, 20f, 4))
{
Corrector textCorrector = new Corrector(textMeasurer);
foreach(FramesDataSet.OffersListRow oneOffer in offers)
{
string authorsString = oneOffer.AuthorsString;
int linesCount = textMeasurer.NoOfLines(oneOffer.AuthorsString);
if (oneOffer.OfferType == 3)
{
authorsString = textCorrector.CorrectHeight(authorsString, 2);
}
else if (oneOffer.Level == 1 || oneOffer.Level == 2)
{
authorsString = textCorrector.CorrectHeight(authorsString, 27);
}
else
{
authorsString = textCorrector.CorrectHeight(authorsString, 13);
}
oneOffer.AuthorsString = authorsString;
}
}
Raport prawie gotowy, pozostał tylko problem z niedziałającym zgodnie z oczekiwaniami PageBreak (zamiast nowej strony jest nowa kolumna). Ale to temat na kolejny artykuł ;).

0 komentarze:
Prześlij komentarz