Exemplo n.º 1
0
        /// <summary>
        /// Set Worksheet print properties
        /// </summary>
        /// <param name="ws">Worksheet object whose print properties to set.</param>
        /// <param name="ci">Culture to use. If undefined, uses current UI culture.</param>
        public static void SetPrintProperties(ExcelWorksheet ws, CultureInfo ci = null)
        {
            ci = ci ?? System.Threading.Thread.CurrentThread.CurrentUICulture;

            ws.HeaderFooter.differentOddEven           = false;
            ws.HeaderFooter.OddHeader.CenteredText     = "&16&\"Arial,Bold\" " + ws.Name;
            ws.HeaderFooter.OddFooter.LeftAlignedText  = "&10&\"Arial,Regular\" " + ws.Workbook.Properties.Company + " " + LocalizedStrings.GetString("Confidential", ci);
            ws.HeaderFooter.OddFooter.CenteredText     = "&10&\"Arial,Regular\" " + ws.Workbook.Properties.Created.ToString("d");
            ws.HeaderFooter.OddFooter.RightAlignedText = "&10&\"Arial,Regular\" " + string.Format(LocalizedStrings.GetString("Page {0} of {1}", ci), ExcelHeaderFooter.PageNumber, ExcelHeaderFooter.NumberOfPages);
            ws.PrinterSettings.RepeatRows         = ws.Cells["1:1"];
            ws.PrinterSettings.ShowGridLines      = true;
            ws.PrinterSettings.FitToPage          = true;
            ws.PrinterSettings.FitToWidth         = 1;
            ws.PrinterSettings.FitToHeight        = 32767;
            ws.PrinterSettings.Orientation        = eOrientation.Landscape;
            ws.PrinterSettings.BottomMargin       = 0.5m;
            ws.PrinterSettings.TopMargin          = 0.5m;
            ws.PrinterSettings.LeftMargin         = 0.5m;
            ws.PrinterSettings.RightMargin        = 0.5m;
            ws.PrinterSettings.HorizontalCentered = true;
        }
        /// <summary>
        ///   Write a formatted many-to-many assignment spreadsheet to an open stream.
        /// </summary>
        /// <param name="stream">The open stream to write the excel workbook to</param>
        /// <param name="table">
        ///   Enumerable array of string[]. Table rows are NOT random access.
        ///   Forward read ONCE only. All string[] rows must be of the same
        ///   length. Header names must be pre-localized. Specifically
        ///   designed for end-to-end streaming.
        /// </param>
        /// <param name="worksheetTabName">The localized worksheet tab name</param>
        /// <param name="wbProps">The workbook properties object to write to the excel workbook</param>
        /// <remarks>
        /// Table format:
        /// <code>
        ///   ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
        ///   │ RHN1 │ RHN2 │ RHNn │ AHN1 │ AHN2 │ AHN2 │ AHN3 │ AHNn │
        ///   │ null │ null │ null │ key1 │ key2 │ key2 │ key3 │ keyN │
        ///   ├──────┼──────┼──────╆━━━━━━┿━━━━━━┿━━━━━━┿━━━━━━┿━━━━━━┥
        ///   │ RH1  │ RH2  │ RH3  ┃  X   │      │      │  X   │  X   │
        ///   │ RH1  │ RH2  │ RH3  ┃  X   │      │      │  X   │  X   │
        ///   │ RH1  │ RH2  │ RH3  ┃  X   │      │      │  X   │  X   │
        ///   └──────┴──────┴──────┸──────┴──────┴──────┴──────┴──────┘
        ///   where:
        ///     RHN  = row header column names
        ///     AHN  = assignment column names
        ///     null = null or empty as field has no meaning for row header columns.
        ///            The last empty cell marks the beginning of the assignment columns.
        ///     key  = keys used upon import of each column (row is hidden in excel)
        ///     RH   = row header value
        ///     Assignment values:
        ///      'X' = row value is currently assigned in the database
        ///      null or empty = row value is not assigned
        /// </code>
        /// </remarks>
        public void Serialize(Stream stream, IEnumerable <string[]> table, string worksheetTabName, WorkbookProperties wbProps)
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream), "Output stream must not be null.");
            }
            if (table == null)
            {
                throw new ArgumentNullException(nameof(table), "Source data must not be null.");
            }
            if (string.IsNullOrWhiteSpace(worksheetTabName))
            {
                throw new ArgumentNullException(nameof(worksheetTabName), "The worksheet tab name must not be empty.");
            }

            // We use the current thread UI culture for localization and restore it upon exit.
            CultureInfo originalUICulture = System.Threading.Thread.CurrentThread.CurrentUICulture;  // for language
            CultureInfo originalCulture   = System.Threading.Thread.CurrentThread.CurrentCulture;    // for region

            try
            {
                using (var pkg = new ExcelPackage(stream))
                {
                    ExcelWorkbook wb      = pkg.Workbook;
                    var           xlprops = ExcelCommon.SetWorkbookProperties(wb, ExcelIdentifier, wbProps);
                    var           ws      = wb.Worksheets.Add(worksheetTabName);

                    int colCount           = 99999;
                    int assignmentColIndex = 0;

                    // Set Header and Data values
                    // Table is enumerable array of string[]. Thus table rows are NOT random access. Forward read ONCE only.
                    int r = 0;
                    foreach (var row in table)
                    {
                        if (r == 0) // header row
                        {
                            colCount = row.Length;
                            for (int c = 0; c < colCount; c++)
                            {
                                ws.Cells[r + 1, c + 1].Value = row[c];
                            }

                            r++;
                            continue;
                        }

                        if (row.Length != colCount)
                        {
                            throw new InvalidDataException("Column count mismatch.");
                        }

                        if (r == 1) // key row
                        {
                            for (int c = 0; c < colCount; c++)
                            {
                                if (string.IsNullOrWhiteSpace(row[c]))
                                {
                                    assignmentColIndex = c + 1;                                    // find index of first assignment column.
                                }
                                ws.Cells[r + 1, c + 1].Value = row[c];
                            }

                            if (assignmentColIndex < 1)
                            {
                                throw new ArgumentNullException(nameof(table), "There is no row header column.");
                            }
                            r++;
                            continue;
                        }

                        for (int c = 0; c < colCount; c++)
                        {
                            ws.Cells[r + 1, c + 1].Value = row[c].AppendSp();
                        }

                        r++;
                    }

                    var rowCount = r;
                    if (rowCount < 3)
                    {
                        throw new ArgumentNullException(nameof(table), "Source data must not be empty.");               // headerRow + keyRow + users count
                    }
                    // Add Checksum column
                    ws.Cells[1, colCount + 1].Value = "CheckSum";
                    for (r = 2; r < rowCount; r++)
                    {
                        var range = ws.Cells[r + 1, assignmentColIndex + 1, r + 1, colCount].Value as object[, ];
                        ws.Cells[r + 1, colCount + 1].Value = EncodeChecksum(range);
                    }

                    ws.Cells.Style.Numberformat.Format = "@"; // All cells have the TEXT format.

                    // Hidden Key Row Formatting
                    using (var range = ws.Cells[2, 1, 2, colCount + 1])
                    {
                        range.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
                        range.Style.Border.Top.Style    = ExcelBorderStyle.Thin;
                        range.Style.Border.Left.Style   = ExcelBorderStyle.Thin;
                        range.Style.Border.Right.Style  = ExcelBorderStyle.Thin;
                        range.Style.Fill.PatternType    = ExcelFillStyle.Solid;
                        range.Style.Fill.BackgroundColor.SetColor(xlprops.Light);
                        range.Style.VerticalAlignment   = ExcelVerticalAlignment.Bottom;
                        range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
                        range.Style.TextRotation        = 90;
                        ws.Row(2).Hidden = true; // Hide 2nd row. This contains the guid keys
                    }

                    // Visible header row formatting
                    using (var range = ws.Cells[1, 1, 1, colCount + 1])
                    {
                        range.Style.Border.Bottom.Style  = ExcelBorderStyle.Thin;
                        range.Style.Border.Top.Style     = ExcelBorderStyle.Thin;
                        range.Style.Border.Left.Style    = ExcelBorderStyle.Thin;
                        range.Style.Border.Right.Style   = ExcelBorderStyle.Thin;
                        range.Style.Font.Bold            = true;
                        range.Style.Fill.Gradient.Type   = ExcelFillGradientType.Linear;
                        range.Style.Fill.Gradient.Degree = 90;
                        range.Style.Fill.Gradient.Color1.SetColor(xlprops.Medium); // TopGradientColor
                        range.Style.Fill.Gradient.Color2.SetColor(xlprops.Light);  // BottomGradientColor
                        range.Style.TextRotation        = 90;
                        range.Style.VerticalAlignment   = ExcelVerticalAlignment.Bottom;
                        range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
                    }

                    // Reset visible row header, header formatting
                    using (var range = ws.Cells[1, 1, 1, assignmentColIndex])
                    {
                        range.Style.TextRotation        = 0;
                        range.Style.VerticalAlignment   = ExcelVerticalAlignment.Center;
                        range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Left;
                    }

                    ws.Protection.IsProtected            = true;
                    ws.Protection.AllowSelectLockedCells = false;
                    using (var range = ws.Cells[3, assignmentColIndex + 1, rowCount, colCount])
                    {
                        range.Style.Locked = false;
                        range.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
                        var val = range.DataValidation.AddListDataValidation();
                        val.ErrorStyle       = ExcelDataValidationWarningStyle.stop;
                        val.AllowBlank       = true;
                        val.ShowErrorMessage = true;
                        // val.ShowDropdown = false; // disable in-cell dropdown...Arrgh! Does't exist. See XML fixups below...
                        val.ErrorTitle = LocalizedStrings.GetString("AssignmentExcel_PopupErrorTitle", "Cell Assignment", wbProps.Culture);
                        val.Error      = LocalizedStrings.GetString("AssignmentExcel_PopupErrorMessage", "Must enter 'X' to assign, or set to empty to unassign.", wbProps.Culture);
                        val.Formula.Values.Add(string.Empty);
                        val.Formula.Values.Add("X");
                        val.Formula.Values.Add("x");

                        var cf = range.ConditionalFormatting.AddEqual();
                        cf.Formula = "\"X\"";
                        cf.Style.Border.Right.Style         = cf.Style.Border.Left.Style = cf.Style.Border.Top.Style = cf.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
                        cf.Style.Border.Right.Color.Color   = cf.Style.Border.Left.Color.Color = cf.Style.Border.Top.Color.Color = cf.Style.Border.Bottom.Color.Color = Color.FromArgb(83, 141, 213);
                        cf.Style.Fill.PatternType           = ExcelFillStyle.Solid; // ExcelFillStyle.Gradient does not exist! Too complicated to hack it with XML.
                        cf.Style.Fill.BackgroundColor.Color = Color.FromArgb(221, 231, 242);
                        cf.Style.Fill.PatternColor.Color    = Color.FromArgb(150, 180, 216);
                        cf.Style.Font.Color.Color           = Color.Brown;
                    }

                    ws.View.FreezePanes(3, assignmentColIndex + 1); // 2,4 refers to the first upper-left cell that is NOT frozen
                    ws.Column(colCount + 1).Hidden = true;          // Hide last col. This contains the 'checksum' flags

                    ExcelCommon.SetPrintProperties(ws, wbProps.Culture);
                    ExcelCommon.DisableCellWarnings(ws);
                    ExcelCommon.HideCellValidationDropdowns(ws);
                    ExcelCommon.AutoFitColumns(ws, 3, false);

                    pkg.Save();
                }
            }
            finally
            {
                System.Threading.Thread.CurrentThread.CurrentUICulture = originalUICulture;
                System.Threading.Thread.CurrentThread.CurrentCulture   = originalCulture;
            }
        }
Exemplo n.º 3
0
        /// <summary>
        /// Create new Property Attribute object with all necessary values pre-computed for fast usability.
        /// </summary>
        /// <param name="p">Property info to use to set values.</param>
        /// <param name="ci">Culture to use for localization of header</param>
        private PropertyAttribute(PropertyInfo p, CultureInfo ci)
        {
            Name         = p.Name;
            PropertyType = p.PropertyType;
            CellType     = p.PropertyType.IsGenericType ? p.PropertyType.GenericTypeArguments[0] : p.PropertyType;
            IsNullable   = p.PropertyType.IsGenericType;
            GetValue     = (o) => p.GetValue(o);
            SetValue     = (o, v) => p.SetValue(o, Cast.To(p.PropertyType, v));

            if (CellType == typeof(bool))
            {
                GetValue = (o) => p.GetValue(o)?.ToString();  // Excel uses TRUE/FALSE. We like True/False
            }
            else if (CellType == typeof(string))
            {
                GetValue = (o) => p.GetValue(o)?.ToString().AppendSp();
                SetValue = (o, v) => p.SetValue(o, v.ToString().TrimSp());
            }
            else if (CellType == typeof(char))  // EPPlus assumes primitive types are always numbers!
            {
                GetValue = (o) => p.GetValue(o)?.ToString().AppendSp();
                SetValue = (o, v) => p.SetValue(o, v?.ToString()?[0]);
            }

            XlColumnAttribute a = p.GetCustomAttribute <XlColumnAttribute>(true);

            if (a == null)
            {
                Header = LocalizedStrings.GetString(p.Name, null, ci);
            }
            else
            {
                if (ci.Name != string.Empty && a.TranslateData)
                {
                    if (CellType == typeof(string) || CellType == typeof(bool))
                    {
                        GetValue = (o) =>
                        {
                            var v = p.GetValue(o);
                            if (v == null)
                            {
                                return(null);
                            }
                            return(LocalizedStrings.GetString(v.ToString(), ci));
                        };
                        SetValue = (o, v) =>
                        {
                            if (!string.IsNullOrWhiteSpace(v as string))
                            {
                                p.SetValue(o, Cast.To(p.PropertyType, LocalizedStrings.ReverseLookup(v.ToString(), ci)));
                            }
                        };
                    }
                    else if (CellType.IsEnum)
                    {
                        // Note: ExcelSerializer restricts values to a limited set of choices.
                        var vals       = Enum.GetValues(CellType);
                        var elookup    = new Dictionary <Enum, string>(vals.Length);
                        var erevlookup = new Dictionary <string, Enum>(vals.Length);
                        foreach (Enum e in vals)
                        {
                            var s = SerializerProperties.LocalizedEnumName(e, ci);
                            elookup.Add(e, s);
                            erevlookup.Add(s, e);
                        }

                        GetValue = (o) =>
                        {
                            var v = p.GetValue(o);
                            if (v == null)
                            {
                                return(null);
                            }
                            return(elookup[(Enum)v]);
                        };
                        SetValue = (o, v) =>
                        {
                            if (!string.IsNullOrWhiteSpace(v as string))
                            {
                                p.SetValue(o, erevlookup[(string)v]);
                            }
                        };
                    }
                }

                Header        = LocalizedStrings.GetString(a.Id, p.Name, ci);
                Format        = a.Format;
                Frozen        = a.Frozen;
                HasFilter     = a.HasFilter;
                Justification = a.Justification;
                Hidden        = a.Hidden;
                TranslateData = a.TranslateData;
                MaxWidth      = a.MaxWidth;

                // Format styling may contain 2 comma delimited fields. The first field contains code char + int
                // where the int represents the number of digits after the decimal (aka precision). The optional 2nd
                // field contains the units string(including leading and trailing whitespace). However if the 2nd
                // field contains an integer, it is used as a relative column index to the column value containing the units.
                if (!string.IsNullOrWhiteSpace(Format) && (Format[0] == 'f' || Format[0] == 'F' ||
                                                           Format[0] == 'n' || Format[0] == 'N' ||
                                                           Format[0] == 'c' || Format[0] == 'C'))
                {
                    var e = Format.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                    if (e.Length > 1 && int.TryParse(e[1].Trim(), out var index))
                    {
                        RelUnitsIndex = index;
                        Format        = e[0];
                    }
                }
            }
        }