我有一个 SSRS 报告,它在 tablix 中包含 20 多列。我们的用户已经确定数据没问题,但他们希望列移动(叹气!)。
重新排列列似乎应该很容易(将第 3 列移动到第 1 列,交换第 4 列和第 5 列等)。但是,拖放似乎不起作用,唯一的解决方案似乎是删除原始列并将其重新插入到正确的位置(并重新应用已为该列创建的任何表达式和格式)。
有没有更简单的方法来做到这一点?请注意,我不需要编程解决方案,而只需要在设计模式下更改一次。
我有一个 SSRS 报告,它在 tablix 中包含 20 多列。我们的用户已经确定数据没问题,但他们希望列移动(叹气!)。
重新排列列似乎应该很容易(将第 3 列移动到第 1 列,交换第 4 列和第 5 列等)。但是,拖放似乎不起作用,唯一的解决方案似乎是删除原始列并将其重新插入到正确的位置(并重新应用已为该列创建的任何表达式和格式)。
有没有更简单的方法来做到这一点?请注意,我不需要编程解决方案,而只需要在设计模式下更改一次。
有一种方法可以通过设计器移动列:
如果您可以阅读 XML(只需了解标签的开始和/或结束位置等),您就可以轻松完成任务。您可以采取以下一系列步骤:
<Tablix Name="Tablix1"> ....</Tablix >
<Textbox Name="...">...</Texbox>
嵌套在标签中的不同“”<TablixCells><TablixCell><CellContents>....
标签<Textbox...>...</Texbox>
您将获得具有新列顺序的新报告。实际上,您需要移动(剪切和粘贴)<TablixCell>
列的整个元素(以及之间的所有内容<TablixCell>
,</TablixCell>
包括<TablixCell>
and</TablixCell>
标记本身)。
例如,要重新排列下面示例中的列以使“产品 ID”列位于“产品名称”列之前,您将选择并剪切“产品名称”单元元素周围的整个部分(从第一个<TablixCell>
到第first </TablixCell>
) 然后将其粘贴到“ProductID”列之后</TablixCell>
。
请注意,Tablix 中定义的每一行都有一套完整的<TablixCell>
元素;每一个都在一个单独的<TablixRow>
元素中。如果您保留默认标题列(设置列名的位置),则第一个<TablixRow>
定义标题行,第二个定义列中的数据,它是您要编辑的数据。重新排列数据列后,您需要对标题列执行相同的操作(如果有),或者只需使用设计器重命名列以匹配列中的数据。
确实,这太复杂了,只需使用设计器在您希望将列移动到的位置插入新列,使用该列的正确数据源设置它,然后删除原始列,移动列可能更容易. 对于下面的示例,您将在Product ID之后插入一个新列,将其设置为ProductName数据源列(在标题行中将其设置为“Product Name”),然后删除左侧的原始Product Name列。
...
<TablixCell>
<CellContents>
<Textbox Name="ProductName">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!ProductName.Value</Value>
<Style />
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>ProductName</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="ProductID">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!ProductID.Value</Value>
<Style />
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>ProductID</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
...
剪切/粘贴后,您将得到:
...
<TablixCell>
<CellContents>
<Textbox Name="ProductID">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!ProductID.Value</Value>
<Style />
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>ProductID</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
<TablixCell>
<CellContents>
<Textbox Name="ProductName">
<CanGrow>true</CanGrow>
<KeepTogether>true</KeepTogether>
<Paragraphs>
<Paragraph>
<TextRuns>
<TextRun>
<Value>=Fields!ProductName.Value</Value>
<Style />
</TextRun>
</TextRuns>
<Style />
</Paragraph>
</Paragraphs>
<rd:DefaultName>ProductName</rd:DefaultName>
<Style>
<Border>
<Color>LightGrey</Color>
<Style>Solid</Style>
</Border>
<PaddingLeft>2pt</PaddingLeft>
<PaddingRight>2pt</PaddingRight>
<PaddingTop>2pt</PaddingTop>
<PaddingBottom>2pt</PaddingBottom>
</Style>
</Textbox>
</CellContents>
</TablixCell>
...
在 RDL 中工作的另一个注意事项:
如果您弄错了,报告将显示错误消息并且不会显示数据。
除非您熟悉 RDL(报表定义语言,一种 XML),否则这些类型的错误在处理有时会导致报表无法使用时会非常令人沮丧。
使用上面提到的设计器中的添加新列和删除旧列的方法要安全得多。这使您远离 RDL,从而减少损坏报告的机会。
我今天遇到这种情况,因为我试图通过拖动 tablix 的列标题来重新排序列,它不起作用!但是,我发现可以拖动一个单元格并(小心地)将其放在另一个单元格上,然后单元格交换。这样,您可以通过交换标题和内容单元格来重新排列列,而无需创建新的空列,如果您不希望报表正文宽度增加并在 PDF 渲染中产生空页面,这会更好,当然它可以修复再次。要拖动单元格,请单击单元格但不要进入编辑模式,然后将鼠标悬停在边框周围并在获得“移动”光标后拖动。这适用于 Visual Studio 2017 可用的报表设计器。
我的解决方案:
using System;
using System.IO;
using System.Linq;
using System.Xml;
namespace MoveSsrsColumns
{
class TablixColumnReorderer
{
readonly XmlDocument _xData = new XmlDocument();
readonly XmlNamespaceManager _nsManager;
readonly XmlElement _tablixNode;
public TablixColumnReorderer(string rdlFileName, string tablixName)
{
using (var fs = new FileStream(rdlFileName, FileMode.Open))
using (var xr = XmlReader.Create(fs))
_xData.Load(xr);
_nsManager = new XmlNamespaceManager(_xData.NameTable);
_nsManager.AddNamespace("def", "http://schemas.microsoft.com/sqlserver/reporting/2010/01/reportdefinition");
_tablixNode =
_xData.SelectNodes(string.Format(TablixXPath, tablixName)_nsManager)
?.Cast<XmlElement>().FirstOrDefault()
?? throw new ApplicationException("Tablix node notfound");
}
const string TablixXPath = @"
/def:Report
/def:ReportSections
/def:ReportSection
/def:Body
/def:ReportItems
/def:Tablix[@Name='{0}']";
const string SearchColumnXPath = @"
def:TablixBody
/def:TablixRows
/def:TablixRow
/def:TablixCells
/def:TablixCell
/def:CellContents
/def:*[@Name='{0}']";
const string ParentTablixCellXPath = "parent::def:CellContents/parent::def:TablixCell";
int FindColumn(string columnControlName)
{
var columnControl = _tablixNode
.SelectNodes(string.Format(SearchColumnXPath, columnControlName), _nsManager)
?.Cast<XmlElement>()
.Single();
if (columnControl==null)
throw new ArgumentException($"Column with control {columnControlName} notfound");
if (!(columnControl.SelectSingleNode(ParentTablixCellXPath, _nsManager) is XmlElement tablixCell))
throw new ArgumentException($"Tablix cell for column with control {columnControlName} notfound");
var columnIndex = ((XmlElement) tablixCell.ParentNode)
?.ChildNodes
.Cast<XmlElement>()
.TakeWhile(e=>e!=tablixCell)
.Count() ?? -1;
if (columnIndex==-1)
throw new ArgumentException($"Cannot get index for column with control {columnControlName}");
return columnIndex;
}
public void SetPosition(string sourceColumnControlName, string destinationColumnControlName)
{
SetPosition(FindColumn(sourceColumnControlName), FindColumn(destinationColumnControlName));
}
public void SetPosition(string sourceColumnControlName, int destinationColumnIndex)
{
SetPosition(FindColumn(sourceColumnControlName), destinationColumnIndex);
}
public void SetPosition(int sourceColumnIndex, string destinationColumnControlName)
{
SetPosition(sourceColumnIndex, FindColumn(destinationColumnControlName));
}
const string TablixCellsXPath = "def:TablixBody/def:TablixColumns";
const string TablixRowCellsXPath = "def:TablixBody/def:TablixRows/def:TablixRow/def:TablixCells";
public void SetPosition(int sourceColumnIndex, int destinationColumnIndex)
{
var tablixColumnsNode = _tablixNode
.SelectSingleNode(TablixCellsXPath, _nsManager) as XmlElement
?? throw new ApplicationException("TablixColumns node notfound");
tablixColumnsNode.InsertBefore(
tablixColumnsNode.ChildNodes[sourceColumnIndex],
tablixColumnsNode.ChildNodes[destinationColumnIndex]
);
var tablixRowsCells = _tablixNode
.SelectNodes(TablixRowCellsXPath, _nsManager)
?.Cast<XmlElement>()
?? throw new ApplicationException("Tablix rows cells notfound");
foreach (var cells in tablixRowsCells)
cells.InsertBefore(
cells.ChildNodes[sourceColumnIndex],
cells.ChildNodes[destinationColumnIndex]
);
}
public void Save(string rdlFileName)
{
using (var fs = new FileStream(rdlFileName, FileMode.Create))
using (var xw = XmlWriter.Create(fs, new XmlWriterSettings
{
Indent = true,
IndentChars = " "
}))
_xData.Save(xw);
}
}
}
用法:
public static void Main(string[] args)
{
var tcr = new TablixColumnReorderer("myreport.rdl", "Tablix1");
tcr.SetPosition("bill_number", 0);
tcr.SetPosition("account", 1);
tcr.SetPosition("to_date", 2);
tcr.Save("myreport#2.rdl");
Console.WriteLine("done");
Console.ReadKey(true);
}