本文章是由機器翻譯。

UI 最前線

Silverlight 列印基礎

Charles Petzold

下載程式碼範例

Charles PetzoldSilverlight 4 新增列印到 [Silverlight] 功能清單中,而且我想要 plunge 中所顯示的小程式,將大微笑放在我的臉上滑鼠右鍵。

程式會呼叫 PrintEllipse,也就是它。MainPage 的 XAML 檔包含一個按鈕,以及圖 1完整顯示的 MainPage 程式碼後置檔案。

圖 1PrintEllipse MainPage 程式碼

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Printing;
using System.Windows.Shapes;

namespace PrintEllipse
{
  public partial class MainPage : UserControl
  {
    public MainPage()
    {
      InitializeComponent();
    }
    void OnButtonClick(object sender, RoutedEventArgs args)
    {
      PrintDocument printDoc = new PrintDocument();
      printDoc.PrintPage += OnPrintPage;
      printDoc.Print("Print Ellipse");
    }
    void OnPrintPage(object sender, PrintPageEventArgs args)
    {
      Ellipse ellipse = new Ellipse
      {
        Fill = new SolidColorBrush(Color.FromArgb(255, 255, 192, 192)),
        Stroke = new SolidColorBrush(Color.FromArgb(255, 192, 192, 255)),
        StrokeThickness = 24    // 1/4 inch
      };
      args.PageVisual = ellipse;
    }
  }
}

請注意 using 指示詞的 System.Windows.Printing。當您按一下按鈕時,程式會建立類型來列印簡單的物件,然後將指派 PrintPage 事件的處理常式。當程式呼叫 Print 方法時,標準 [列印] 對話方塊隨即出現。使用者可以利用這個機會若要設定那一台印表機使用並設定為列印,例如縱向或橫向模式的各種屬性。

當使用者按一下 [列印] 對話方塊列印時,則程式會收到 PrintPage 事件處理常式呼叫。此特定程式會回應建立橢圓形項目,並將之設定為事件引數的 PageVisual 屬性。(我故意選擇淺粉色彩因此程式將不會使用太多您的筆跡。)很快就頁面會脫離您填入巨人橢圓形的印表機。

您可以執行此程式從我的網站上,在bit.ly/dU9B7k並將它簽出您自己。從這份文件的所有原始程式碼當然都還有可下載。

如果您的印表機就像大部分的印表機,內部硬體禁止之故列印到紙張的最邊緣。印表機通常會有內建的內建邊界,在其中無法列印。 列印是限制為 「 可列印區域 」,小於完整頁面的大小。

您會注意到與這個程式是用的橢圓形會出現在頁面上,在可列印區域內完整而發生很明顯地這種情況與程式的最小工作。頁面的可列印區域的行為就更像在螢幕上的容器項目:它只裁剪子系,當項目具有的大小超過 [] 區域。某些更複雜的圖形環境,例如 Windows 簡報基礎 (WPF) — 幾乎也不會運作 (但,不用多說,WPF 提供了更多列印控制項和彈性比 Silverlight)。

來列印簡單和事件

除了 PrintPage 事件,來列印簡單也定義了 BeginPrint 和 EndPrint 事件,但這些都不是與 PrintPage 幾乎一樣重要。BeginPrint 事件通知列印工作的開頭。它發生於在使用者結束標準的列印對話方塊按 [列印] 按鈕,並讓程式有機會執行初始化。BeginPrint 處理常式的呼叫接下來的 PrintPage 處理常式的第一個呼叫。

想要列印特定的列印工作中的多個網頁的程式可以這麼做。在每個呼叫 PrintPage 處理常式時,PrintPageEventArgs 的 HasMorePages 屬性一開始是設定為 false。與網頁完成處理常式時,它只可以設定屬性設為 true 可發出信號必須列印一個以上的多個頁面。然後會再次呼叫 PrintPage。來列印簡單物件會維護一次就遞增遵循每個呼叫來 PrintPage 處理常式的 PrintedPageCount 屬性。

PrintPage 處理常式結束與時 HasMorePages 設定為其預設值為 false,列印工作位於上方,並引發 EndPrint 事件,讓程式有機會執行清理工作。在列印的程序; 期間發生錯誤時也引發 EndPrint 事件 EndPrintEventArgs 的錯誤屬性屬於型別例外狀況。

印表機座標

所示的程式碼圖 1將橢圓形為設定為 24,並且如果您計算的列印的結果,您會發現它是為四分之一英吋寬。如您所知,Silverlight 程式通常調整大小的圖形物件和控制項完全以像素為單位。然而,如果包含印表機,座標和大小是以與裝置無關的 1/96 英吋為單位。印表機的實際解析度,不論從 Silverlight 程式印表機永遠顯示為 96 DPI 裝置。

您可能知道,此坐標系的 96 單位為英寸用於整個 WPF 中,凡單位有時稱為獨立于設備的圖元"。此值 96 DPI 的不是任意選擇:根據預設,Windows 會假設您的視訊顯示有以英吋 96 點數,因此在許多情況下 WPF 程式實際繪製像素為單位。CSS 規格假設視訊顯示有 96 DPI 解析度,而這個值用來轉換之間的像素、 英吋和公釐。96 的值也是一個方便的數字轉換字型大小,通常在點為單位或 1/72nd 英吋中指定。一點代表與裝置無關的像素的四分之三。

PrintPageEventArgs 有兩個方便僅屬性也 1/96 英吋為單位調整報表大小:型別大小的 PrintableArea 提供工作區] 頁面上,可列印區域的維度和 PageMargins 的型別粗細是頂端左右及底部的無法列印邊緣的寬度。相加兩個一起 (以正確的方法),取得完整的紙張大小。

我的印表機,與標準 8.5 x 11 英吋的紙載入和縱向模式設定時,報告 791 x 993 的 PrintableArea。PageMargins 屬性的四個值是 12 (左圖)、 6 (上圖)、 12 (右圖) 和 56 (下方)。如果您的 791、 12 和 12 的水平數值加總,您會得到 815。垂直值是 994、 6 和 56,總和為 1,055。我不確定這些值與 816 和 1,056 取得方式是乘以英吋 96 x 中的紙張大小的值一單位不同的原因。

當印表機設定為橫向模式時,會交換 PrintableArea 和 PageMargins 所報告的水平和垂直維度。的確,檢查 PrintableArea 屬性是 Silverlight 程式可以判斷印表機是否處於縱向或橫向模式的唯一方法。列印程式的任何項目自動對齊並旋轉這種模式而定。

通常當您列印在現實生活中的項目,您會定義稍微大於不可列印的邊界的邊界。您如何進行這 Silverlight 中?首先,我以為很簡單,只要在您要列印的項目上設定邊界屬性。此毛利率會計算方式是以所需的總邊界 (單位為 1/96 英吋) 開始,減去可從 PrintPageEventArgs 的 PageMargins 屬性的值。這種方法並未運作正常,但是正確的解決方案就是幾乎一樣容易。PrintEllipseWithMargins 程式 (其中您可以在執行bit.ly/fCBs3X) 是第一個程式一樣,不同之處在於 Margin 屬性設定在橢圓形和橢圓形然後將設定為框線,也就填滿的可列印區域的子系。或者,您可以設定與邊框距離屬性框線上。圖 2示範新的 OnPrintPage 方法。

圖 2OnPrintPage 方法,以計算邊界

void OnPrintPage(object sender, PrintPageEventArgs args)
{
  Thickness margin = new Thickness
  {
    Left = Math.Max(0, 96 - args.PageMargins.Left),
    Top = Math.Max(0, 96 - args.PageMargins.Top),
    Right = Math.Max(0, 96 - args.PageMargins.Right),
    Bottom = Math.Max(0, 96 - args.PageMargins.Bottom)
  };
  Ellipse ellipse = new Ellipse
  {
    Fill = new SolidColorBrush(Color.FromArgb(255, 255, 192, 192)),
    Stroke = new SolidColorBrush(Color.FromArgb(255, 192, 192, 255)),
    StrokeThickness = 24,   // 1/4 inch
    Margin = margin
  };
  Border border = new Border();
  border.Child = ellipse;
  args.PageVisual = border;
}

PageVisual 物件

沒有特殊的圖形方法或與印表機相關聯的圖形類別。您 「 繪製 」 項目在印表機頁面的相同方式 「 繪製 」 項目上顯現的視訊影像,也就是藉由組合從 FrameworkElement 衍生的物件的視覺化樹狀結構。此樹狀結構可以包含面板項目,包括畫布。若要列印的視覺化樹狀結構,將設定為 PrintPageEventArgs 的 PageVisual 屬性的最上層的項目。(PageVisual 定義為 UIElement,也就是 FrameworkElement 的父類別,但在實際的情況下,您將會設定為 PageVisual 的所有項目會從 FrameworkElement 衍生)。

幾乎每一個類別都衍生自 FrameworkElement 有非一般的版面配置的目的的 MeasureOverride 和 ArrangeOverride 方法的實作。在其 MeasureOverride 方法中,項目決定其所需的大小,有時候藉由呼叫其子系的量值的方法來判斷及其子系的所需的大小。ArrangeOverride 方法,在項目會以相對於本身及其子系排列藉由呼叫 「 兒童排列方法。

當您將項目設定為 PrintPageEventArgs 的 PageVisual 屬性時,Silverlight 列印系統會呼叫量值 PrintableArea 大小的最上層項目上。這是如何 (例如) 表示橢圓形或邊界而自動調整大小可列印的頁面範圍。

不過,您也可以設定 PageVisual 屬性已經是在程式的視窗中顯示的視覺化樹狀結構的一部分的項目。在這種情況下,列印系統不會呼叫該項目上的量值,但是而是使用的度量單位和已經決定了視訊顯示的版面配置。這可讓您列印的項目從您的程式視窗以合理的精確度,但這也表示您的列印可能會裁剪成頁面的大小。

您可以不用多說,在您列印時,項目上設定明確的寬度和高度屬性,您可以使用 PrintableArea 大小幫助。

放大和旋轉

下一個程式我花費是較為困難比我猜想開啟出。目標是一種程式,可讓使用者列印受 Silverlight 任何影像檔 — 也就是 PNG 和 JPEG 檔案,儲存在使用者的本機電腦上。此程式會使用 OpenFileDialog 類別來載入這些檔案。基於安全性考量,OpenFileDialog 只會傳回 FileInfo 物件,可讓程式開啟檔案。會提供任何檔名或目錄。

我想這個程式來列印在頁面上 (不含預設的邊界) 越大越點陣圖,而不需變更點陣圖的長寬比。通常這是嵌入式管理單元:影像項目的預設自動縮放模式是一致,這表示點陣圖會自動縮放越大越但不會變形。

不過,我決定我不想要求使用者在特別設定直向或橫向模式和特定映像 commensurate 的印表機上。如果印表機已設為直向模式,且影像已超過它的高度,我想要在直向列印頁面上橫向列印的影像。這個小功能立即可使程式更為複雜。

如果我在撰寫 WPF 程式若要執行這項操作,程式本身無法切換印表機為直印] 或 [橫印模式。但這並不在 Silverlight 中。定義印表機介面時,讓只有該使用者可以變更設定的。

同樣地,如果我在撰寫 WPF 程式,或者我可以有 LayoutTransform 上設定要將它旋轉 90 度的影像項目。會再而無法容納於頁面上,調整大小旋轉的影像項目和點陣圖本身會被調整以配合影像項目。

但是 Silverlight 並不支援 LayoutTransform。Silverlight 唯一支援 RenderTransform,所以如果項目必須以容納在直向模式中,影像項目列印的橫向影像旋轉的影像也必須以手動方式調整為橫印頁面的尺寸。

您可以嘗試在我第一次嘗試bit.ly/eMHOsB。OnPrintPage 方法會建立影像項目,並設定 [展開] 屬性為 [無],這表示影像項目顯示點陣圖,以像素大小,這在印表機上表示每個像素會被假設為 1/96 英吋。程式然後旋轉、 調整大小並將該影像項目轉譯藉由計算轉換功能,它會套用到影像項目的 RenderTransform 屬性。

這類程式碼的困難部分是,不用多說,數學,因此很美觀到與印表機設定為 [直向和橫向模式使用直向和橫向影像的程式。

不過,它是特別令人不悅看到失敗的大型影像的程式。您也可以嘗試自行含除以 96) 時,有些大尺寸的影像比以英吋頁面的大小。影像會顯示在正確的大小,而不是完整。

這裡發生什麼?然而,其實沒有我曾看過在視訊的顯示器上的項目。請記住,只顯示之項目的的方式,以及不出現方式給配置系統會影響 RenderTransform。到版面配置] 系統中,我正在顯示點陣圖影像項目具有延伸設定為 「 無 」 表示影像項目不會和點陣圖本身一樣大。如果點陣圖大於 [印表機],然後該影像項目部分需要都不呈現了,而且它會,事實上,加以裁剪,不論適當地縮小影像項目 RenderTransform。

您可以嘗試在我第二次嘗試bit.ly/g4HJ1C,會稍有不同的策略。OnPrintPage 方法會顯示在圖 3。影像項目提供明確的寬度和高度設定,使其完全導出的顯示區域的大小。因為它是全部都在頁面的可列印區域內,不會加以裁剪。自動縮放模式設定為填滿,即表示點陣圖填滿影像項目,不論其長寬比。如果不會旋轉影像項目,一個維度的正確大小,並與其他維度必須套用的減少大小的縮放比例。如果也必須旋轉影像項目,縮放比例因數必須足以容納不同長寬比的旋轉影像項目。

圖 3列印 PrintImage 中的圖像

void OnPrintPage(object sender, PrintPageEventArgs args)
{
  // Find the full size of the page
  Size pageSize = 
    new Size(args.PrintableArea.Width 
    + args.PageMargins.Left + args.PageMargins.Right,
    args.PrintableArea.Height 
    + args.PageMargins.Top + args.PageMargins.Bottom);

  // Get additional margins to bring the total to MARGIN (= 96)
  Thickness additionalMargin = new Thickness
  {
    Left = Math.Max(0, MARGIN - args.PageMargins.Left),
    Top = Math.Max(0, MARGIN - args.PageMargins.Top),
    Right = Math.Max(0, MARGIN - args.PageMargins.Right),
    Bottom = Math.Max(0, MARGIN - args.PageMargins.Bottom)
  };

  // Find the area for display purposes
  Size displayArea = 
    new Size(args.PrintableArea.Width 
    - additionalMargin.Left - additionalMargin.Right,
    args.PrintableArea.Height 
    - additionalMargin.Top - additionalMargin.Bottom);

  bool pageIsLandscape = displayArea.Width > displayArea.Height;
  bool imageIsLandscape = bitmap.PixelWidth > bitmap.PixelHeight;

  double displayAspectRatio = displayArea.Width / displayArea.Height;
  double imageAspectRatio = (double)bitmap.PixelWidth / bitmap.PixelHeight;

  double scaleX = Math.Min(1, imageAspectRatio / displayAspectRatio);
  double scaleY = Math.Min(1, displayAspectRatio / imageAspectRatio);

  // Calculate the transform matrix
  MatrixTransform transform = new MatrixTransform();

  if (pageIsLandscape == imageIsLandscape)
  {
    // Pure scaling
    transform.Matrix = new Matrix(scaleX, 0, 0, scaleY, 0, 0);
  }
  else
  {
    // Scaling with rotation
    scaleX *= pageIsLandscape ?
displayAspectRatio : 1 / 
      displayAspectRatio;
    scaleY *= pageIsLandscape ?
displayAspectRatio : 1 / 
      displayAspectRatio;
    transform.Matrix = new Matrix(0, scaleX, -scaleY, 0, 0, 0);
  }

  Image image = new Image
  {
    Source = bitmap,
    Stretch = Stretch.Fill,
    Width = displayArea.Width,
    Height = displayArea.Height,
    RenderTransform = transform,
    RenderTransformOrigin = new Point(0.5, 0.5),
    HorizontalAlignment = HorizontalAlignment.Center,
    VerticalAlignment = VerticalAlignment.Center,
    Margin = additionalMargin,
  };

  Border border = new Border
  {
    Child = image,
  };

  args.PageVisual = border;
}

程式碼是當然似乎有點混亂,和我懷疑可能有簡單化不立即明顯 — 但這也適用於各種規模的點陣圖。

旋轉點陣圖本身而非影像項目為另一種方法。從已載入的 BitmapImage 物件和交換的水平和垂直維度與第二個 WritableBitmap 建立 WriteableBitmap。然後複製到第二個資料列和交換的資料行的第一個 WriteableBitmap 的所有像素。

多個行事曆頁面

衍生自使用者控制項是在 Silverlight 程式設計建立可重複使用控制項沒有很多的麻煩事一深受喜愛技巧。大部分的使用者控制項是在 XAML 中定義的視覺化樹狀結構。

您也可以從其中定義列印視覺化樹狀結構的使用者控制項!此項技術乃在 PrintCalendar 程式中,您可以嘗試在bit.ly/dIwSsn。您輸入起始月份和結束的月份,然後程式列印落於上述範圍的所有月份至網頁的一個月。您可以到你的城牆磁帶頁面,並將它們標記,就像真實壁掛式的行事曆。

之後我 PrintImage 程式的經驗,我不想遵守邊界或方向。 相反地,我之所以會包含一個按鈕,將責任放入的使用者,如所示圖 4

圖 4PrintCalendar 按鈕

定義行事曆頁面的使用者控制項稱為 CalendarPage,和 XAML 檔案會顯示在圖 5。TextBlock 上方顯示的月份和年份。這後面會每週天數的七個資料行的第二個方格和六個資料列最多六週或一個月中的部分數週。

圖 5CalendarPage 版面配置

<UserControl x:Class="PrintCalendar.CalendarPage"
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  FontSize="36">  
  <Grid x:Name="LayoutRoot" Background="White">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <TextBlock Name="monthYearText" 
      Grid.Row="0"
       FontSize="48"
       HorizontalAlignment="Center" />
    <Grid Name="dayGrid" 
      Grid.Row="1">
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
      </Grid.ColumnDefinitions>
      <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
      </Grid.RowDefinitions>
    </Grid>
  </Grid>
</UserControl>

與大部分的使用者控制項衍生項目,不同的是 CalendarPage 定義建構函式具有參數,如所示圖 6

圖 6CalendarPage 程式碼後置建構函式

public CalendarPage(DateTime date)
{
  InitializeComponent();
  monthYearText.Text = date.ToString("MMMM yyyy");
  int row = 0;
  int col = (int)new DateTime(date.Year, date.Month, 1).DayOfWeek;
  for (int day = 0; day < DateTime.DaysInMonth(date.Year, date.Month); day++)
  {
    TextBlock txtblk = new TextBlock
    {
      Text = (day + 1).ToString(),
      HorizontalAlignment = HorizontalAlignment.Left,
      VerticalAlignment = VerticalAlignment.Top
    };
    Border border = new Border
    {
      BorderBrush = blackBrush,
      BorderThickness = new Thickness(2),
      Child = txtblk
    };
    Grid.SetRow(border, row);
    Grid.SetColumn(border, col);
    dayGrid.Children.Add(border);
    if (++col == 7)
    {
      col = 0;
      row++;
    }
  }
  if (col == 0)
    row--;
  if (row < 5)
    dayGrid.RowDefinitions.RemoveAt(0);
  if (row < 4)
    dayGrid.RowDefinitions.RemoveAt(0);
}

參數是日期時間,且建構函式會使用的月份和年份] 屬性來建立每一天的月份包含 TextBlock 框線。 這些是每個分派的 Grid.Row 和 Grid.Column 附加屬性,然後再加入至方格。 如您所知,通常月五個唯一範圍週,和偶爾二月僅有四個星期,因此如果不需要 RowDefinition 物件實際上從方格移除。

使用者控制項衍生項目通常不需要使用參數的建構函式因為它們通常形成較大的視覺化樹狀結構的部分。 但不能這樣用 CalendarPage。 相反地,PrintPage 處理常式只會指派 CalendarPage 的新執行個體至 PrintPageEventArgs 的 PageVisual 屬性。 以下是完整的處理常式中,清楚地說明 [多少工作正在執行 CalendarPage 的主體:

args.PageVisual = new CalendarPage(dateTime);
args.HasMorePages = dateTime < dateTimeEnd;
dateTime = dateTime.AddMonths(1);

將列印選項加入至程式因此常被視為經歷工作牽涉到許多程式碼。若要能定義大部分的列印頁面的 XAML 檔中使整個圖表很少 frightful。

Charles Petzold 是 longtime 特約編輯 MSDN 雜誌。他的新活頁簿,「 程式設計在 Windows 電話 7 」 (微軟出版品,2010年),是用作免費下載在bit.ly/cpebookpdf

多虧了要對下列技術專家,來檢閱這份文件:Saied KhanahmadiRobert Lyon