C#design-在没有空接口的情况下,如何将类和枚举本质上分组到一个列表中?
我正在设计一个文本渲染器。我正在构建如何将字符串拆分为适合文本框的行的逻辑。我想这样做的方法是首先将整个字符串拆分为“单词”、“空格”和“换行符”,然后构建一个算法来测量一行上可以容纳多少,然后再移动到下一行 然而,“Word”必须是一个类/结构,因为它想要保存额外的属性,如“FontStyle”、“color”等。“Space”和“NewLine”只是标记,它们不需要任何数据 目前,我有以下框架:C#design-在没有空接口的情况下,如何将类和枚举本质上分组到一个列表中?,c#,list,design-patterns,interface,C#,List,Design Patterns,Interface,我正在设计一个文本渲染器。我正在构建如何将字符串拆分为适合文本框的行的逻辑。我想这样做的方法是首先将整个字符串拆分为“单词”、“空格”和“换行符”,然后构建一个算法来测量一行上可以容纳多少,然后再移动到下一行 然而,“Word”必须是一个类/结构,因为它想要保存额外的属性,如“FontStyle”、“color”等。“Space”和“NewLine”只是标记,它们不需要任何数据 目前,我有以下框架: interface ILineComponent { } struct Word : ILin
interface ILineComponent
{
}
struct Word : ILineComponent
{
public string Text;
public FontStyle Style;
public Colour Colour;
public Word(string text, FontStyle style, Colour colour)
{
Text = text;
Style = style;
Colour = colour;
}
}
struct Space : ILineComponent
{
}
struct NewLine : ILineComponent
{
}
struct Line
{
public List<ILineComponent> Segments;
public Line(List<ILineComponent> segments)
{
Segments = segments;
}
}
但这种设计违反了法律
如何更好地设计它?空接口不仅违反了CA1040。它还违反了DRY的干净代码原则,因为您很可能会在代码的多个位置使用if/else语句。如果不根据空接口的存在做出一些决定,那么对于空接口,您还会做什么 所以,是的,让我们摆脱它。感谢您关心编译器警告并尝试编写更好的代码 首先,我认为
空间的定义是不完整的。如果没有字体,空间无法确定其宽度。因此,我认为空格应该类似于Word
。也许它不需要颜色,当然也不需要文本作为参数
struct Space : ILineComponent
{
public readonly string Text = " ";
public FontStyle Style;
public Word(FontStyle style)
{
Style = style;
}
}
<>我不认为这是一个特殊的例子:<代码> Word < /代码>。稍后你会看到,为什么会这样。这是因为我提议的界面
而且我认为这个代码有误导性
foreach (ILineComponent component in line)
由于您还不知道将有多少行,因此变量line
不正确。这应该是componentsOfText
或类似内容。在循环内部,您可以在需要时启动新行
接下来,我建议您将这三个组件需要做的所有事情都放到接口中。据我所知,那就是:
interface ILineComponent
{
int Measure();
bool IsTrimmedAtEndOfLine;
bool TerminatesLine;
}
对于换行符
,Measure()
只返回0
然后,您的代码如下所示:
// IList<LineComponent> is a line
// IList< -"- > are many lines
// Maybe you want to define an alias for that, or even a class
IList<IList<ILineComponent>> lines = new ...;
int remainingSpace = 2000; // px or whatever
while (componentsOfText.Length > 0)
{
component = componentsOfText[0];
var space = component.Measure();
if (space > remainingSpace)
{
// finish current line and start a new one,
// i.e. add a new List<LineComponent>,
// reset remaining space,
// do the line post processing (e.g. space width adjustment)
// Do not remove component from the list
continue;
}
if (component.TerminatesLine)
{
// Finish current line and start a new line
// just as before
}
remainingSpace -= space;
componentsOfText.Remove(component);
}
我用托马斯的答案来帮助构建更强大的东西
助手类:
internal enum LineComponentType { Word, Space, NewLine }
internal interface ILineComponent
{
float Length { get; }
LineComponentType Type { get; }
Colour Colour { get; }
}
internal class NewLine : ILineComponent
{
public float Length => 0;
public LineComponentType Type => LineComponentType.NewLine;
public Colour Colour => Colour.Transparent;
}
internal class Space : ILineComponent
{
public Space(HFont font, FontStyle fontStyle)
{
Length = font.MeasureString(' ', fontStyle).x;
}
public float Length { get; }
public LineComponentType Type => LineComponentType.Space;
public Colour Colour => Colour.Transparent;
public override string ToString() => " ";
}
internal class Word : ILineComponent
{
private readonly HFont _font;
public Word(string text, HFont font, FontStyle style, Colour colour)
{
Text = text;
_font = font;
Style = style;
Colour = colour;
Length = font.MeasureString(text, style).x;
}
public float Length { get; }
public LineComponentType Type => LineComponentType.Word;
public string Text { get; }
public FontStyle Style { get; }
public Colour Colour { get; }
public float MeasureCharAtIndex(int index) => _font.MeasureString(Text[index], Style).x;
public Word SubWord(int startIndex) => new Word(Text.Substring(startIndex), _font, Style, Colour);
public Word SubWord(int startIndex, int length) => new Word(Text.Substring(startIndex, length), _font, Style, Colour);
public override string ToString() => Text;
}
用于渲染的主类:
public class HText : IRectGraphic
{
//...
private List<List<ILineComponent>> SplitTextToLines(string text)
{
//Convert text into a list of IComponents
var allComponents = new List<ILineComponent>();
text.Replace("\r\n", "\n");
text.Replace("\r", "\n");
while (!text.IsNullOrEmpty())
{
if (text[0] == ' ')
{
allComponents.Add(new Space(Font, FontStyle));
text = text.Remove(0, 1);
}
else if (text[0] == '\n')
{
allComponents.Add(new NewLine());
text = text.Remove(0, 1);
}
else //it's a word
{
var words = text.Split(new[] { '\n', ' ' });
allComponents.Add(new Word(words[0], Font, FontStyle, Colour));
text = text.Remove(0, words[0].Length);
}
}
var lines = new List<List<ILineComponent>>();
//Split IComponents into lines
var currentLine = new List<ILineComponent>();
bool oneLetterWiderThanWholeLine = false;
while (!allComponents.IsEmpty())
{
var component = allComponents[0];
switch (component.Type)
{
case LineComponentType.Word:
var word = (Word)component;
if (currentLine.Sum(c => c.Length) + word.Length <= w) //if it fits, add it to the current line
{
//if we started a new line with a space then a word, remove the space
if (lines.Count != 0 && currentLine.Count == 1 && currentLine[0].Type == LineComponentType.Space)
currentLine.RemoveAt(0);
currentLine.Add(component);
allComponents.RemoveAt(0);
}
else if (currentLine.Count == 0) //if doesn't fit, but it is the first word in a line, we will split and wrap it
{
int splitAt = 0;
float width = 0;
float nextCharLength;
while (width + (nextCharLength = word.MeasureCharAtIndex(splitAt)) <= w)
{
width += nextCharLength;
splitAt++;
}
if (splitAt == 0) //if one letter is too wide to fit, display it anyway
{
splitAt = 1;
oneLetterWiderThanWholeLine = true;
}
var word1 = word.SubWord(0, splitAt);
var word2 = word.SubWord(splitAt);
currentLine.Add(word1);
lines.Add(currentLine);
currentLine = new List<ILineComponent>();
allComponents.RemoveAt(0);
allComponents.Insert(0, word2);
}
else //push the word to the next line
{
lines.Add(currentLine);
currentLine = new List<ILineComponent>();
//we don't add the next word to the next line yet - it might not fit
}
break;
case LineComponentType.Space:
if (currentLine.Sum(c => c.Length) + component.Length > w)
{
lines.Add(currentLine);
currentLine = new List<ILineComponent>();
}
currentLine.Add(component);
allComponents.RemoveAt(0);
break;
case LineComponentType.NewLine:
lines.Add(currentLine);
currentLine = new List<ILineComponent>();
allComponents.RemoveAt(0);
break;
default:
throw new HException("Htext/SplitTextToLines: Unhandled component type {0}", component.Type);
}
}
if (currentLine.Count > 0)
lines.Add(currentLine);
//Size warnings
if (lines.Count * Font.BiggestChar.y > h)
HConsole.Log("HText/SetVertices: lines total height ({0}) is bigger than text box height ({1})", lines.Count * Font.BiggestChar.y, h);
if (oneLetterWiderThanWholeLine)
HConsole.Log("HText/SetVertices: a single letter is beyond the text box width ({0})", w);
return lines;
}
private void SetVertices()
{
var vertices = new List<Vertex>();
var lines = SplitTextToLines(Text);
var dest = new Rect();
switch (VAlignment)
{
case VAlignment.Top:
dest.y = 0;
break;
case VAlignment.Centre:
dest.y = (h - lines.Count * Font.BiggestChar.y) / 2;
break;
case VAlignment.Bottom:
dest.y = h - lines.Count * Font.BiggestChar.y;
break;
case VAlignment.Fill:
dest.y = 0;
break;
default:
throw new HException("HText/SetVertices: alignment {0} was not catered for.", VAlignment);
}
foreach (var line in lines)
{
switch (HAlignment)
{
case HAlignment.LeftJustified:
dest.x = 0;
break;
case HAlignment.Centred:
dest.x = (w - line.Sum(c => c.Length)) / 2;
break;
case HAlignment.RightJustified:
dest.x = w - line.Sum(c => c.Length);
break;
case HAlignment.FullyJustified:
dest.x = 0;
break;
default:
throw new HException("HText/SetVertices: alignment {0} was not catered for.", HAlignment);
}
foreach (var com in line)
foreach (char c in com.ToString())
{
var source = Font.CharPositionsNormalised[System.Drawing.FontStyle.Regular][c];
dest.w = Font.CharPositions[System.Drawing.FontStyle.Regular][c].w;
dest.h = Font.CharPositions[System.Drawing.FontStyle.Regular][c].h;
vertices.Add(HF.Geom.QuadToTris(
new Vertex(dest.TopLeft, com.Colour, source.TopLeft),
new Vertex(dest.TopRight, com.Colour, source.TopRight),
new Vertex(dest.BottomLeft, com.Colour, source.BottomLeft),
new Vertex(dest.BottomRight, com.Colour, source.BottomRight)
));
dest.x += dest.w;
}
dest.y += Font.BiggestChar.y;
}
_vertices = vertices.ToArray();
}
public virtual void Render(ref Matrix4 projection, ref Matrix4 modelView)
{
if (w == 0 || h == 0)
return;
if (HV.LastBoundTexture != Texture.ID)
{
OpenGL.BindTexture(TextureTarget.Texture2D, Texture.ID);
HV.LastBoundTexture = Texture.ID;
}
if (HV.LastBoundVertexBuffer != VertexBuffer)
Bind();
if (_verticesChanged)
{
SetVertices();
_verticesChanged = false;
OpenGL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * Vertex.STRIDE, _vertices, BufferUsageHint.StreamDraw);
}
var mv = Matrix4.Translate(ref modelView, x, y, 0);
Shader.Render(ref projection, ref mv, _vertices.Length, PrimitiveType.Triangles);
}
}
公共类HText:IRectGraphic
{
//...
私有列表拆分文本列表(字符串文本)
{
//将文本转换为IComponents列表
var allComponents=新列表();
text.Replace(“\r\n”,“\n”);
文本。替换(“\r”和“\n”);
而(!text.IsNullOrEmpty())
{
如果(文本[0]='')
{
添加(新空格(字体、字体样式));
text=text.Remove(0,1);
}
else if(文本[0]='\n')
{
添加(新换行符());
text=text.Remove(0,1);
}
否则,这是一个词
{
var words=text.Split(新[]{'\n',''});
添加(新单词(单词[0],字体,字体样式,颜色));
text=text.Remove(0,单词[0]。长度);
}
}
变量行=新列表();
//将IComponents拆分为行
var currentLine=新列表();
bool oneletterwidthanwholeline=假;
而(!allComponents.IsEmpty())
{
var分量=所有分量[0];
开关(组件类型)
{
案例LineComponentType。Word:
变量词=(词)成分;
if(currentLine.Sum(c=>c.Length)+单词长度c.Length)+组件长度>w)
{
行。添加(当前行);
currentLine=新列表();
}
currentLine.Add(组件);
移除所有组件(0);
打破
案例LineComponentType.NewLine:
行。添加(当前行);
currentLine=新列表();
移除所有组件(0);
打破
违约:
抛出新的HException(“Htext/splittexttoline:未处理的组件类型{0}”,component.type);
}
}
如果(currentLine.Count>0)
行。添加(当前行);
//尺寸警告
if(lines.Count*Font.BiggestChar.y>h)
HConsole.Log(“HText/SetVertices:lines总高度({0})大于文本框高度({1})”,lines.Count*Font.BiggestChar.y,h);
如果(一个字母宽或整行)
Log(“HText/SetVertices:单个字母超出文本框宽度({0})”,w);
回流线;
}
私有void SetVertices()
{
var顶点=新列表();
变量行=拆分文本行(文本);
var dest=new Rect();
开关(有效)
{
案例有效期。顶部:
目的地y=0;
打破
案例验证中心:
dest.y=(h-lines.Count*Font.BiggestChar.y)/2;
打破
箱子有效期。底部:
dest.y=h-lines.Count*Font.BiggestChar.y;
打破
案例有效期。填写:
目的地y=0;
打破
违约:
抛出新的HException(“HText/SetVertices:alignment{0}不适合。”,VAlignment);
}
foreach(行中的var行)
{
开关(HAlignment)
{
case HAlignment.LeftJustified:
目标x=0;
打破
案例HAlignment.中心:
dest.x=(w-line.Sum(c=>c.Length))/2;
打破
case HAlignment.RightJustified:
dest.x=w-行和(c=>c.Length);
打破
案例哈尔
internal enum LineComponentType { Word, Space, NewLine }
internal interface ILineComponent
{
float Length { get; }
LineComponentType Type { get; }
Colour Colour { get; }
}
internal class NewLine : ILineComponent
{
public float Length => 0;
public LineComponentType Type => LineComponentType.NewLine;
public Colour Colour => Colour.Transparent;
}
internal class Space : ILineComponent
{
public Space(HFont font, FontStyle fontStyle)
{
Length = font.MeasureString(' ', fontStyle).x;
}
public float Length { get; }
public LineComponentType Type => LineComponentType.Space;
public Colour Colour => Colour.Transparent;
public override string ToString() => " ";
}
internal class Word : ILineComponent
{
private readonly HFont _font;
public Word(string text, HFont font, FontStyle style, Colour colour)
{
Text = text;
_font = font;
Style = style;
Colour = colour;
Length = font.MeasureString(text, style).x;
}
public float Length { get; }
public LineComponentType Type => LineComponentType.Word;
public string Text { get; }
public FontStyle Style { get; }
public Colour Colour { get; }
public float MeasureCharAtIndex(int index) => _font.MeasureString(Text[index], Style).x;
public Word SubWord(int startIndex) => new Word(Text.Substring(startIndex), _font, Style, Colour);
public Word SubWord(int startIndex, int length) => new Word(Text.Substring(startIndex, length), _font, Style, Colour);
public override string ToString() => Text;
}
public class HText : IRectGraphic
{
//...
private List<List<ILineComponent>> SplitTextToLines(string text)
{
//Convert text into a list of IComponents
var allComponents = new List<ILineComponent>();
text.Replace("\r\n", "\n");
text.Replace("\r", "\n");
while (!text.IsNullOrEmpty())
{
if (text[0] == ' ')
{
allComponents.Add(new Space(Font, FontStyle));
text = text.Remove(0, 1);
}
else if (text[0] == '\n')
{
allComponents.Add(new NewLine());
text = text.Remove(0, 1);
}
else //it's a word
{
var words = text.Split(new[] { '\n', ' ' });
allComponents.Add(new Word(words[0], Font, FontStyle, Colour));
text = text.Remove(0, words[0].Length);
}
}
var lines = new List<List<ILineComponent>>();
//Split IComponents into lines
var currentLine = new List<ILineComponent>();
bool oneLetterWiderThanWholeLine = false;
while (!allComponents.IsEmpty())
{
var component = allComponents[0];
switch (component.Type)
{
case LineComponentType.Word:
var word = (Word)component;
if (currentLine.Sum(c => c.Length) + word.Length <= w) //if it fits, add it to the current line
{
//if we started a new line with a space then a word, remove the space
if (lines.Count != 0 && currentLine.Count == 1 && currentLine[0].Type == LineComponentType.Space)
currentLine.RemoveAt(0);
currentLine.Add(component);
allComponents.RemoveAt(0);
}
else if (currentLine.Count == 0) //if doesn't fit, but it is the first word in a line, we will split and wrap it
{
int splitAt = 0;
float width = 0;
float nextCharLength;
while (width + (nextCharLength = word.MeasureCharAtIndex(splitAt)) <= w)
{
width += nextCharLength;
splitAt++;
}
if (splitAt == 0) //if one letter is too wide to fit, display it anyway
{
splitAt = 1;
oneLetterWiderThanWholeLine = true;
}
var word1 = word.SubWord(0, splitAt);
var word2 = word.SubWord(splitAt);
currentLine.Add(word1);
lines.Add(currentLine);
currentLine = new List<ILineComponent>();
allComponents.RemoveAt(0);
allComponents.Insert(0, word2);
}
else //push the word to the next line
{
lines.Add(currentLine);
currentLine = new List<ILineComponent>();
//we don't add the next word to the next line yet - it might not fit
}
break;
case LineComponentType.Space:
if (currentLine.Sum(c => c.Length) + component.Length > w)
{
lines.Add(currentLine);
currentLine = new List<ILineComponent>();
}
currentLine.Add(component);
allComponents.RemoveAt(0);
break;
case LineComponentType.NewLine:
lines.Add(currentLine);
currentLine = new List<ILineComponent>();
allComponents.RemoveAt(0);
break;
default:
throw new HException("Htext/SplitTextToLines: Unhandled component type {0}", component.Type);
}
}
if (currentLine.Count > 0)
lines.Add(currentLine);
//Size warnings
if (lines.Count * Font.BiggestChar.y > h)
HConsole.Log("HText/SetVertices: lines total height ({0}) is bigger than text box height ({1})", lines.Count * Font.BiggestChar.y, h);
if (oneLetterWiderThanWholeLine)
HConsole.Log("HText/SetVertices: a single letter is beyond the text box width ({0})", w);
return lines;
}
private void SetVertices()
{
var vertices = new List<Vertex>();
var lines = SplitTextToLines(Text);
var dest = new Rect();
switch (VAlignment)
{
case VAlignment.Top:
dest.y = 0;
break;
case VAlignment.Centre:
dest.y = (h - lines.Count * Font.BiggestChar.y) / 2;
break;
case VAlignment.Bottom:
dest.y = h - lines.Count * Font.BiggestChar.y;
break;
case VAlignment.Fill:
dest.y = 0;
break;
default:
throw new HException("HText/SetVertices: alignment {0} was not catered for.", VAlignment);
}
foreach (var line in lines)
{
switch (HAlignment)
{
case HAlignment.LeftJustified:
dest.x = 0;
break;
case HAlignment.Centred:
dest.x = (w - line.Sum(c => c.Length)) / 2;
break;
case HAlignment.RightJustified:
dest.x = w - line.Sum(c => c.Length);
break;
case HAlignment.FullyJustified:
dest.x = 0;
break;
default:
throw new HException("HText/SetVertices: alignment {0} was not catered for.", HAlignment);
}
foreach (var com in line)
foreach (char c in com.ToString())
{
var source = Font.CharPositionsNormalised[System.Drawing.FontStyle.Regular][c];
dest.w = Font.CharPositions[System.Drawing.FontStyle.Regular][c].w;
dest.h = Font.CharPositions[System.Drawing.FontStyle.Regular][c].h;
vertices.Add(HF.Geom.QuadToTris(
new Vertex(dest.TopLeft, com.Colour, source.TopLeft),
new Vertex(dest.TopRight, com.Colour, source.TopRight),
new Vertex(dest.BottomLeft, com.Colour, source.BottomLeft),
new Vertex(dest.BottomRight, com.Colour, source.BottomRight)
));
dest.x += dest.w;
}
dest.y += Font.BiggestChar.y;
}
_vertices = vertices.ToArray();
}
public virtual void Render(ref Matrix4 projection, ref Matrix4 modelView)
{
if (w == 0 || h == 0)
return;
if (HV.LastBoundTexture != Texture.ID)
{
OpenGL.BindTexture(TextureTarget.Texture2D, Texture.ID);
HV.LastBoundTexture = Texture.ID;
}
if (HV.LastBoundVertexBuffer != VertexBuffer)
Bind();
if (_verticesChanged)
{
SetVertices();
_verticesChanged = false;
OpenGL.BufferData(BufferTarget.ArrayBuffer, _vertices.Length * Vertex.STRIDE, _vertices, BufferUsageHint.StreamDraw);
}
var mv = Matrix4.Translate(ref modelView, x, y, 0);
Shader.Render(ref projection, ref mv, _vertices.Length, PrimitiveType.Triangles);
}
}