using System; using System.Drawing; using System.ComponentModel; using System.Collections.Generic; using FastReport.Utils; using System.Windows.Forms; using System.Drawing.Drawing2D; using System.Drawing.Design; namespace FastReport { /// /// Base class for all bands. /// public abstract partial class BandBase : BreakableComponent, IParent { #region Fields private ChildBand child; private ReportComponentCollection objects; private FloatCollection guides; private bool startNewPage; private bool firstRowStartsNewPage; private bool printOnBottom; private bool keepChild; private string outlineExpression; private int rowNo; private int absRowNo; private bool isFirstRow; private bool isLastRow; private bool repeated; private bool updatingLayout; private bool flagUseStartNewPage; private bool flagCheckFreeSpace; private bool flagMustBreak; private int savedOriginalObjectsCount; private float reprintOffset; private string beforeLayoutEvent; private string afterLayoutEvent; private int repeatBandNTimes = 1; #endregion #region Properties /// /// This event occurs before the band layouts its child objects. /// public event EventHandler BeforeLayout; /// /// This event occurs after the child objects layout was finished. /// public event EventHandler AfterLayout; /// /// Gets or sets a value indicating that the band should be printed from a new page. /// /// /// New page is not generated when printing very first group or data row. This is made to avoid empty /// first page. /// [DefaultValue(false)] [Category("Behavior")] public bool StartNewPage { get { return startNewPage; } set { startNewPage = value; } } /// /// Gets or sets a value that determines the number of repetitions of the same band. /// [Category("Behavior")] [DefaultValue(1)] public int RepeatBandNTimes { get { return repeatBandNTimes; } set { repeatBandNTimes = value; } } /// /// Gets or sets a value indicating that the first row can start a new report page. /// /// /// Use this property if is set to true. Normally the new page /// is not started when printing the first data row, to avoid empty first page. /// [DefaultValue(true)] [Category("Behavior")] public bool FirstRowStartsNewPage { get { return firstRowStartsNewPage; } set { firstRowStartsNewPage = value; } } /// /// Gets or sets a value indicating that the band should be printed on the page bottom. /// [DefaultValue(false)] [Category("Behavior")] public bool PrintOnBottom { get { return printOnBottom; } set { printOnBottom = value; } } /// /// Gets or sets a value indicating that the band should be printed together with its child band. /// [DefaultValue(false)] [Category("Behavior")] public bool KeepChild { get { return keepChild; } set { keepChild = value; } } /// /// Gets or sets an outline expression. /// /// /// /// Outline is a tree control displayed in the preview window. It represents the prepared report structure. /// Each outline node can be clicked to navigate to the item in the prepared report. /// /// /// To create the outline, set this property to any valid expression that represents the outline node text. /// This expression will be calculated when band is about to print, and its value will be added to the /// outline. Thus, nodes' hierarchy in the outline is similar to the bands' hierarchy /// in a report. That means there will be the main and subordinate outline nodes, corresponding /// to the main and subordinate bands in a report (a report with two levels of data or with groups can /// exemplify the point). /// /// [Category("Navigation")] [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] public string OutlineExpression { get { return outlineExpression; } set { outlineExpression = value; } } /// /// Gets or sets a child band that will be printed right after this band. /// /// /// Typical use of child band is to print several objects that can grow or shrink. It also can be done /// using the shift feature (via property), but in some cases it's not possible. /// [Browsable(false)] public ChildBand Child { get { return child; } set { SetProp(child, value); child = value; } } /// /// Gets a collection of report objects belongs to this band. /// [Browsable(false)] public ReportComponentCollection Objects { get { return objects; } } /// /// Gets a value indicating that band is reprinted on a new page. /// /// /// This property is applicable to the DataHeaderBand and GroupHeaderBand only. /// It returns true if its RepeatOnAllPages property is true and band is /// reprinted on a new page. /// [Browsable(false)] public bool Repeated { get { return repeated; } set { repeated = value; // set this flag for child bands as well BandBase child = Child; while (child != null) { child.Repeated = value; child = child.Child; } } } /// /// Gets or sets a script event name that will be fired before the band layouts its child objects. /// [Category("Build")] public string BeforeLayoutEvent { get { return beforeLayoutEvent; } set { beforeLayoutEvent = value; } } /// /// Gets or sets a script event name that will be fired after the child objects layout was finished. /// [Category("Build")] public string AfterLayoutEvent { get { return afterLayoutEvent; } set { afterLayoutEvent = value; } } /// public override float AbsLeft { get { return IsRunning ? base.AbsLeft : Left; } } /// public override float AbsTop { get { return IsRunning ? base.AbsTop : Top; } } /// /// Gets or sets collection of guide lines for this band. /// [Browsable(false)] public FloatCollection Guides { get { return guides; } set { guides = value; } } /// /// Gets a row number (the same value returned by the "Row#" system variable). /// /// /// This property can be used when running a report. It may be useful to print hierarchical /// row numbers in a master-detail report, like this: /// 1.1 /// 1.2 /// 2.1 /// 2.2 /// To do this, put the Text object on a detail data band with the following text in it: /// [Data1.RowNo].[Data2.RowNo] /// [Browsable(false)] public int RowNo { get { return rowNo; } set { rowNo = value; if (Child != null) Child.RowNo = value; } } /// /// Gets an absolute row number (the same value returned by the "AbsRow#" system variable). /// [Browsable(false)] public int AbsRowNo { get { return absRowNo; } set { absRowNo = value; if (Child != null) Child.AbsRowNo = value; } } /// /// Gets a value indicating that this is the first data row. /// [Browsable(false)] public bool IsFirstRow { get { return isFirstRow; } set { isFirstRow = value; } } /// /// Gets a value indicating that this is the last data row. /// [Browsable(false)] public bool IsLastRow { get { return isLastRow; } set { isLastRow = value; } } internal bool HasBorder { get { return !Border.Equals(new Border()); } } internal bool HasFill { get { return !Fill.IsTransparent; } } internal DataBand ParentDataBand { get { Base c = Parent; while (c != null) { if (c is DataBand) return c as DataBand; if (c is ReportPage && (c as ReportPage).Subreport != null) c = (c as ReportPage).Subreport; c = c.Parent; } return null; } } internal bool FlagUseStartNewPage { get { return flagUseStartNewPage; } set { flagUseStartNewPage = value; } } internal bool FlagCheckFreeSpace { get { return flagCheckFreeSpace; } set { flagCheckFreeSpace = value; // set flag for child bands as well BandBase child = Child; while (child != null) { child.FlagCheckFreeSpace = value; child = child.Child; } } } internal bool FlagMustBreak { get { return flagMustBreak; } set { flagMustBreak = value; } } internal float ReprintOffset { get { return reprintOffset; } set { reprintOffset = value; } } internal float PageWidth { get { ReportPage page = Page as ReportPage; if (page != null) return page.WidthInPixels - (page.LeftMargin + page.RightMargin) * Units.Millimeters; return 0; } } #endregion #region IParent Members /// public virtual void GetChildObjects(ObjectCollection list) { foreach (ReportComponentBase obj in objects) { list.Add(obj); } if (!IsRunning) list.Add(child); } /// public virtual bool CanContain(Base child) { if (IsRunning) return child is ReportComponentBase; return ((child is ReportComponentBase && !(child is BandBase)) || child is ChildBand); } /// public virtual void AddChild(Base child) { if (child is ChildBand && !IsRunning) Child = child as ChildBand; else objects.Add(child as ReportComponentBase); } /// public virtual void RemoveChild(Base child) { if (child is ChildBand && this.child == child as ChildBand) Child = null; else objects.Remove(child as ReportComponentBase); } /// public virtual int GetChildOrder(Base child) { return objects.IndexOf(child as ReportComponentBase); } /// public virtual void SetChildOrder(Base child, int order) { int oldOrder = child.ZOrder; if (oldOrder != -1 && order != -1 && oldOrder != order) { if (order > objects.Count) order = objects.Count; if (oldOrder <= order) order--; objects.Remove(child as ReportComponentBase); objects.Insert(order, child as ReportComponentBase); UpdateLayout(0, 0); } } /// public virtual void UpdateLayout(float dx, float dy) { if (updatingLayout) return; updatingLayout = true; try { RectangleF remainingBounds = new RectangleF(0, 0, Width, Height); remainingBounds.Width += dx; remainingBounds.Height += dy; foreach (ReportComponentBase c in Objects) { if ((c.Anchor & AnchorStyles.Right) != 0) { if ((c.Anchor & AnchorStyles.Left) != 0) c.Width += dx; else c.Left += dx; } else if ((c.Anchor & AnchorStyles.Left) == 0) { c.Left += dx / 2; } if ((c.Anchor & AnchorStyles.Bottom) != 0) { if ((c.Anchor & AnchorStyles.Top) != 0) c.Height += dy; else c.Top += dy; } else if ((c.Anchor & AnchorStyles.Top) == 0) { c.Top += dy / 2; } switch (c.Dock) { case DockStyle.Left: c.Bounds = new RectangleF(remainingBounds.Left, remainingBounds.Top, c.Width, remainingBounds.Height); remainingBounds.X += c.Width; remainingBounds.Width -= c.Width; break; case DockStyle.Top: c.Bounds = new RectangleF(remainingBounds.Left, remainingBounds.Top, remainingBounds.Width, c.Height); remainingBounds.Y += c.Height; remainingBounds.Height -= c.Height; break; case DockStyle.Right: c.Bounds = new RectangleF(remainingBounds.Right - c.Width, remainingBounds.Top, c.Width, remainingBounds.Height); remainingBounds.Width -= c.Width; break; case DockStyle.Bottom: c.Bounds = new RectangleF(remainingBounds.Left, remainingBounds.Bottom - c.Height, remainingBounds.Width, c.Height); remainingBounds.Height -= c.Height; break; case DockStyle.Fill: c.Bounds = remainingBounds; remainingBounds.Width = 0; remainingBounds.Height = 0; break; } } } finally { updatingLayout = false; } } #endregion #region Public Methods /// public override void Assign(Base source) { base.Assign(source); BandBase src = source as BandBase; Guides.Assign(src.Guides); StartNewPage = src.StartNewPage; FirstRowStartsNewPage = src.FirstRowStartsNewPage; PrintOnBottom = src.PrintOnBottom; KeepChild = src.KeepChild; OutlineExpression = src.OutlineExpression; BeforeLayoutEvent = src.BeforeLayoutEvent; AfterLayoutEvent = src.AfterLayoutEvent; RepeatBandNTimes = src.RepeatBandNTimes; IsLastRow = src.IsLastRow; } internal virtual void UpdateWidth() { // update band width. It is needed for anchor/dock ReportPage page = Page as ReportPage; if (page != null && !(page.UnlimitedWidth && IsDesigning)) { if (page.Columns.Count <= 1 || !IsColumnDependentBand) Width = PageWidth; } } /// public override List Validate() { return new List(); } /// public override void Serialize(FRWriter writer) { BandBase c = writer.DiffObject as BandBase; base.Serialize(writer); if (writer.SerializeTo == SerializeTo.Preview) return; if (StartNewPage != c.StartNewPage) writer.WriteBool("StartNewPage", StartNewPage); if (FirstRowStartsNewPage != c.FirstRowStartsNewPage) writer.WriteBool("FirstRowStartsNewPage", FirstRowStartsNewPage); if (PrintOnBottom != c.PrintOnBottom) writer.WriteBool("PrintOnBottom", PrintOnBottom); if (KeepChild != c.KeepChild) writer.WriteBool("KeepChild", KeepChild); if (OutlineExpression != c.OutlineExpression) writer.WriteStr("OutlineExpression", OutlineExpression); if (Guides.Count > 0) writer.WriteValue("Guides", Guides); if (BeforeLayoutEvent != c.BeforeLayoutEvent) writer.WriteStr("BeforeLayoutEvent", BeforeLayoutEvent); if (AfterLayoutEvent != c.AfterLayoutEvent) writer.WriteStr("AfterLayoutEvent", AfterLayoutEvent); if (RepeatBandNTimes != c.RepeatBandNTimes) writer.WriteInt("RepeatBandNTimes", RepeatBandNTimes); } internal bool IsColumnDependentBand { get { BandBase b = this; if (b is ChildBand) { while (b is ChildBand) { b = b.Parent as BandBase; } } if (b is DataHeaderBand || b is DataBand || b is DataFooterBand || b is GroupHeaderBand || b is GroupFooterBand || b is ColumnHeaderBand || b is ColumnFooterBand || b is ReportSummaryBand) return true; return false; } } #endregion #region Report Engine internal void SetUpdatingLayout(bool value) { updatingLayout = value; } /// public override string[] GetExpressions() { List expressions = new List(); expressions.AddRange(base.GetExpressions()); if (!String.IsNullOrEmpty(OutlineExpression)) expressions.Add(OutlineExpression); return expressions.ToArray(); } /// public override void SaveState() { base.SaveState(); savedOriginalObjectsCount = Objects.Count; SetRunning(true); SetDesigning(false); OnBeforePrint(EventArgs.Empty); foreach (ReportComponentBase obj in Objects) { obj.SaveState(); obj.SetRunning(true); obj.SetDesigning(false); obj.OnBeforePrint(EventArgs.Empty); } //Report.Engine.TranslatedObjectsToBand(this); // apply even style if (RowNo % 2 == 0) { ApplyEvenStyle(); foreach (ReportComponentBase obj in Objects) { obj.ApplyEvenStyle(); } } } /// public override void RestoreState() { OnAfterPrint(EventArgs.Empty); base.RestoreState(); while (Objects.Count > savedOriginalObjectsCount) { Objects[Objects.Count - 1].Dispose(); } SetRunning(false); ReportComponentCollection collection_clone = new ReportComponentCollection(); Objects.CopyTo(collection_clone); foreach (ReportComponentBase obj in collection_clone) { obj.OnAfterPrint(EventArgs.Empty); obj.RestoreState(); obj.SetRunning(false); } } /// public override float CalcHeight() { OnBeforeLayout(EventArgs.Empty); // sort objects by Top ReportComponentCollection sortedObjects = Objects.SortByTop(); // calc height of each object float[] heights = new float[sortedObjects.Count]; for (int i = 0; i < sortedObjects.Count; i++) { ReportComponentBase obj = sortedObjects[i]; float height = obj.Height; if (obj.Visible && (obj.CanGrow || obj.CanShrink)) { float height1 = obj.CalcHeight(); if ((obj.CanGrow && height1 > height) || (obj.CanShrink && height1 < height)) height = height1; } heights[i] = height; } // calc shift amounts float[] shifts = new float[sortedObjects.Count]; for (int i = 0; i < sortedObjects.Count; i++) { ReportComponentBase parent = sortedObjects[i]; float shift = heights[i] - parent.Height; if (shift == 0) continue; for (int j = i + 1; j < sortedObjects.Count; j++) { ReportComponentBase child = sortedObjects[j]; if (child.ShiftMode == ShiftMode.Never) continue; if (child.Top >= parent.Bottom - 1e-4) { if (child.ShiftMode == ShiftMode.WhenOverlapped && (child.Left > parent.Right - 1e-4 || parent.Left > child.Right - 1e-4)) continue; float parentShift = shifts[i]; float childShift = shifts[j]; if (shift > 0) childShift = Math.Max(shift + parentShift, childShift); else childShift = Math.Min(shift + parentShift, childShift); shifts[j] = childShift; } } } // update location and size of each component, calc max height float maxHeight = 0; for (int i = 0; i < sortedObjects.Count; i++) { ReportComponentBase obj = sortedObjects[i]; DockStyle saveDock = obj.Dock; obj.Dock = DockStyle.None; obj.Height = heights[i]; obj.Top += shifts[i]; if (obj.Visible && obj.Bottom > maxHeight) maxHeight = obj.Bottom; obj.Dock = saveDock; } if ((CanGrow && maxHeight > Height) || (CanShrink && maxHeight < Height)) Height = maxHeight; // perform grow to bottom foreach (ReportComponentBase obj in Objects) { if (obj.GrowToBottom) { obj.Height = Height - obj.Top; // reserve place for border if (IsLastRow && obj.Border.Lines.HasFlag(BorderLines.Bottom)) obj.Height -= Border.BottomLine.Width; } } OnAfterLayout(EventArgs.Empty); return Height; } /// public void AddLastToFooter(BreakableComponent breakTo) { float maxTop = (AllObjects[0] as ComponentBase).Top; for (int i = 0; i < AllObjects.Count; i++) { if (AllObjects[i] is ComponentBase) { ComponentBase obj = AllObjects[i] as ComponentBase; if (obj.Top > maxTop && !(obj is DataFooterBand)) maxTop = obj.Top; } } float breakLine = maxTop; List pasteList = new List(); foreach (ReportComponentBase obj in Objects) if (obj.Bottom > breakLine) pasteList.Add(obj); int itemsBefore = breakTo.AllObjects.Count; foreach (ReportComponentBase obj in pasteList) { if (obj.Top < breakLine) { BreakableComponent breakComp = Activator.CreateInstance(obj.GetType()) as BreakableComponent; breakComp.AssignAll(obj); breakComp.Parent = breakTo; breakComp.CanGrow = true; breakComp.CanShrink = false; breakComp.Height -= breakLine - obj.Top; breakComp.Top = 0; obj.Height = breakLine - obj.Top; (obj as BreakableComponent).Break(breakComp); } else { obj.Top -= breakLine; obj.Parent = breakTo; continue; } } float minTop = (breakTo.AllObjects[0] as ComponentBase).Top; float maxBottom = 0; for (int i = itemsBefore; i < breakTo.AllObjects.Count; i++) if (breakTo.AllObjects[i] is ComponentBase) if ((breakTo.AllObjects[i] as ComponentBase).Top < minTop && breakTo.AllObjects[i] is ReportComponentBase && !(breakTo.AllObjects[i] is Table.TableCell)) minTop = (breakTo.AllObjects[i] as ComponentBase).Top; for (int i = itemsBefore; i < breakTo.AllObjects.Count; i++) if (breakTo.AllObjects[i] is ComponentBase) if ((breakTo.AllObjects[i] as ComponentBase).Bottom > maxBottom && breakTo.AllObjects[i] is ReportComponentBase && !(breakTo.AllObjects[i] is Table.TableCell)) maxBottom = (breakTo.AllObjects[i] as ComponentBase).Bottom; for (int i = 0; i < itemsBefore; i++) if (breakTo.AllObjects[i] is ComponentBase) (breakTo.AllObjects[i] as ComponentBase).Top += maxBottom - minTop; breakTo.Height += maxBottom - minTop; Height -= maxBottom - minTop; } /// public override bool Break(BreakableComponent breakTo) { // first we find the break line. It's a minimum Top coordinate of the object that cannot break. float breakLine = Height; float excessiveHeight = 0; float maxExcessiveHeight = 0; Dictionary excessiveHeights = new Dictionary(); bool breakLineFound = true; do { breakLineFound = true; foreach (ReportComponentBase obj in Objects) { bool canBreak = true; if (obj.Top < breakLine && obj.Bottom > breakLine) { canBreak = false; BreakableComponent breakable = obj as BreakableComponent; if (breakable != null && breakable.CanBreak) { using (BreakableComponent clone = Activator.CreateInstance(breakable.GetType()) as BreakableComponent) { clone.AssignAll(breakable); clone.Height = breakLine - clone.Top; // to allow access to the Report clone.Parent = breakTo; canBreak = clone.Break(null, out excessiveHeight); if (excessiveHeight > 0) { excessiveHeights.Add(clone.Bounds, excessiveHeight); maxExcessiveHeight = Math.Max(maxExcessiveHeight, excessiveHeight); } } } } if (!canBreak) { breakLine = Math.Min(obj.Top, breakLine); // enumerate objects again breakLineFound = false; break; } } } while (!breakLineFound); // now break the components int i = 0; while (i < Objects.Count) { ReportComponentBase obj = Objects[i]; if (obj.Bottom > breakLine) { if (obj.Top < breakLine) { BreakableComponent breakComp = Activator.CreateInstance(obj.GetType()) as BreakableComponent; breakComp.AssignAll(obj); breakComp.Parent = breakTo; breakComp.CanGrow = true; breakComp.CanShrink = false; breakComp.Height -= breakLine - obj.Top; breakComp.Top = 0; obj.Height = breakLine - obj.Top; (obj as BreakableComponent).Break(breakComp); } else { float currentExcessiveHeight = 0; foreach(var item in excessiveHeights) { if ((Math.Round(obj.Left, 2) <= Math.Round(item.Key.Left, 2) && Math.Round(obj.Right, 2) >= Math.Round(item.Key.Left, 2)) || (Math.Round(obj.Left, 2) >= Math.Round(item.Key.Left, 2) && Math.Round(item.Key.Right, 2) >= Math.Round(obj.Left, 2))) currentExcessiveHeight = Math.Max(currentExcessiveHeight, item.Value); } // (case: object with Anchor = bottom on a breakable band) // in case of bottom anchor, do not move the object. It will be moved automatically when we decrease the band height if ((obj.Anchor & AnchorStyles.Bottom) == 0) obj.Top -= (breakLine - currentExcessiveHeight); obj.Parent = breakTo; continue; } } i++; } Height = breakLine; breakTo.Height -= (breakLine - maxExcessiveHeight); return Objects.Count > 0; } /// public override void GetData() { base.GetData(); FRCollectionBase list = new FRCollectionBase(); Objects.CopyTo(list); foreach (ReportComponentBase obj in list) { obj.GetData(); obj.OnAfterData(); // break the component if it is of BreakableComponent an has non-empty BreakTo property if (obj is BreakableComponent && (obj as BreakableComponent).BreakTo != null && (obj as BreakableComponent).BreakTo.GetType() == obj.GetType()) (obj as BreakableComponent).Break((obj as BreakableComponent).BreakTo); } OnAfterData(); } internal virtual bool IsEmpty() { return true; } private void AddBookmark(ReportComponentBase obj) { if (Report != null) Report.Engine.AddBookmark(obj.Bookmark); } internal void AddBookmarks() { AddBookmark(this); foreach (ReportComponentBase obj in Objects) { AddBookmark(obj); } } /// public override void InitializeComponent() { base.InitializeComponent(); AbsRowNo = 0; } /// /// This method fires the BeforeLayout event and the script code connected to the BeforeLayoutEvent. /// /// Event data. public void OnBeforeLayout(EventArgs e) { if (BeforeLayout != null) BeforeLayout(this, e); InvokeEvent(BeforeLayoutEvent, e); } /// /// This method fires the AfterLayout event and the script code connected to the AfterLayoutEvent. /// /// Event data. public void OnAfterLayout(EventArgs e) { if (AfterLayout != null) AfterLayout(this, e); InvokeEvent(AfterLayoutEvent, e); } #endregion /// /// Initializes a new instance of the class with default settings. /// public BandBase() { objects = new ReportComponentCollection(this); guides = new FloatCollection(); beforeLayoutEvent = ""; afterLayoutEvent = ""; outlineExpression = ""; CanBreak = false; ShiftMode = ShiftMode.Never; if (BaseName.EndsWith("Band")) BaseName = ClassName.Remove(ClassName.IndexOf("Band")); SetFlags(Flags.CanMove | Flags.CanChangeOrder | Flags.CanChangeParent | Flags.CanCopy | Flags.CanGroup, false); FlagUseStartNewPage = true; FlagCheckFreeSpace = true; } } }