derekthornton
asked on
Threading Issue
Alright. I'm trying to thread something ..and it's worked pretty good so far. This procedure works just fine without threading, but when I run the threading, it tells me that the Main function is already in use elsewhere. Has anyone experienced this before? The code is a little long to put on here... But the problem seems to be with the main thread, not the actual thread.
Can you post the actual error you got?
ASKER
An unhandled exception of type 'System.InvalidOperationEx ception' occurred in system.windows.forms.dll
Additional information: The object is currently in use elsewhere.
The cursor is placed at the last curly bracket of the
Public Static Void Main()
method.
Additional information: The object is currently in use elsewhere.
The cursor is placed at the last curly bracket of the
Public Static Void Main()
method.
Hi derekthornton !
If i undestand this problem correctly, than you have a synchronization problem on your hands.
You accessing some code inside one thread from another thread wile process is still working. To solve this i suggest that you lock part of code that is crytical and must be safe, like this:
public void ExecuteBatch()
{
lock{
BatchDialog();
}
}
public void ExecuteSelectionBatch()
{
lock{
BatchSelection();
}
}
In this case, code is thread safe and it is impossible for two different threads to use this code in the same time.
Hope this will help.
Good Luck!
If i undestand this problem correctly, than you have a synchronization problem on your hands.
You accessing some code inside one thread from another thread wile process is still working. To solve this i suggest that you lock part of code that is crytical and must be safe, like this:
public void ExecuteBatch()
{
lock{
BatchDialog();
}
}
public void ExecuteSelectionBatch()
{
lock{
BatchSelection();
}
}
In this case, code is thread safe and it is impossible for two different threads to use this code in the same time.
Hope this will help.
Good Luck!
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
I understand what you're saying, but the above didn't help. So I don't think it was the entire problem. Any other suggestions?
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
--------------------------
--------------------------
I owe too much to this forum. But I am learning fast.
--------------------------
using System;
using System.IO;
using System.Runtime.InteropServ
using System.Drawing;
using System.Drawing.Imaging;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using DevComponents.DotNetBar;
using System.Threading;
namespace OCR
{
/// <summary>
/// Summary description for Viewer.
/// </summary>
public class Viewer : System.Windows.Forms.Form
{
#region Windows Designer Variables
private DevComponents.DotNetBar.Do
private DevComponents.DotNetBar.Do
private DevComponents.DotNetBar.Do
private DevComponents.DotNetBar.Do
private DevComponents.DotNetBar.Do
private System.Windows.Forms.Image
private System.Windows.Forms.Split
private System.ComponentModel.ICon
private OCR.ImageControl ImageControl;
#endregion
#region Instance Variables
private const short mIP = 0;
private const short mRP = 1;
private const short mOCRSTATE_IDLE = 0;
private const short mOCRSTATE_ACTIVE = 1;
private const short mOCRSTATE_ABORT = 2;
private const short mOCRSTATE_END = 3;
private System.Windows.Forms.RichT
private System.Windows.Forms.Split
private System.Windows.Forms.ListB
private ImageInfo Picture = new ImageInfo();
private PANEINFO[] mPane = new PANEINFO[mRP];
private ImageBatch ImageBatch = new ImageBatch();
private Rectangle theRectangle = new Rectangle
(new Point(0, 0), new Size(0, 0));
Thread OCRThread;
#endregion
#region Form Methods
public Viewer()
{
InitializeComponent();
Picture.Viewer = this.ImageControl;
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
public static void Main()
{
Application.Run(new Viewer());
}
#endregion
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Cont
System.Resources.ResourceM
this.menu = new DevComponents.DotNetBar.Do
this.barBottomDockSite = new DevComponents.DotNetBar.Do
this.imageList1 = new System.Windows.Forms.Image
this.barLeftDockSite = new DevComponents.DotNetBar.Do
this.barRightDockSite = new DevComponents.DotNetBar.Do
this.barTopDockSite = new DevComponents.DotNetBar.Do
this.ImageControl = new OCR.ImageControl();
this.splitter1 = new System.Windows.Forms.Split
this.rtfBox = new System.Windows.Forms.RichT
this.splitter2 = new System.Windows.Forms.Split
this.lstPicture = new System.Windows.Forms.ListB
this.SuspendLayout();
//
// menu
//
this.menu.AutoDispatchShor
this.menu.AutoDispatchShor
this.menu.AutoDispatchShor
this.menu.AutoDispatchShor
this.menu.AutoDispatchShor
this.menu.AutoDispatchShor
this.menu.AutoDispatchShor
this.menu.AutoDispatchShor
this.menu.BottomDockSite = this.barBottomDockSite;
this.menu.DefinitionName = "Viewer.menu.xml";
this.menu.Images = this.imageList1;
this.menu.ImagesLarge = null;
this.menu.ImagesMedium = null;
this.menu.LeftDockSite = this.barLeftDockSite;
this.menu.ParentForm = this;
this.menu.RightDockSite = this.barRightDockSite;
this.menu.TopDockSite = this.barTopDockSite;
this.menu.ItemClick += new System.EventHandler(this.m
//
// barBottomDockSite
//
this.barBottomDockSite.Acc
this.barBottomDockSite.Bac
this.barBottomDockSite.Doc
this.barBottomDockSite.Loc
this.barBottomDockSite.Nam
this.barBottomDockSite.Siz
this.barBottomDockSite.Tab
this.barBottomDockSite.Tab
//
// imageList1
//
this.imageList1.ImageSize = new System.Drawing.Size(16, 16);
this.imageList1.ImageStrea
this.imageList1.Transparen
//
// barLeftDockSite
//
this.barLeftDockSite.Acces
this.barLeftDockSite.Backg
this.barLeftDockSite.Dock = System.Windows.Forms.DockS
this.barLeftDockSite.Locat
this.barLeftDockSite.Name = "barLeftDockSite";
this.barLeftDockSite.Size = new System.Drawing.Size(0, 356);
this.barLeftDockSite.TabIn
this.barLeftDockSite.TabSt
//
// barRightDockSite
//
this.barRightDockSite.Acce
this.barRightDockSite.Back
this.barRightDockSite.Dock
this.barRightDockSite.Loca
this.barRightDockSite.Name
this.barRightDockSite.Size
this.barRightDockSite.TabI
this.barRightDockSite.TabS
//
// barTopDockSite
//
this.barTopDockSite.Access
this.barTopDockSite.Backgr
this.barTopDockSite.Dock = System.Windows.Forms.DockS
this.barTopDockSite.Locati
this.barTopDockSite.Name = "barTopDockSite";
this.barTopDockSite.Size = new System.Drawing.Size(622, 49);
this.barTopDockSite.TabInd
this.barTopDockSite.TabSto
//
// ImageControl
//
this.ImageControl.AutoScro
this.ImageControl.BackColo
this.ImageControl.Dock = System.Windows.Forms.DockS
this.ImageControl.Image = null;
this.ImageControl.ImageSiz
this.ImageControl.Location
this.ImageControl.Name = "ImageControl";
this.ImageControl.Size = new System.Drawing.Size(460, 356);
this.ImageControl.TabIndex
//
// splitter1
//
this.splitter1.Location = new System.Drawing.Point(460, 49);
this.splitter1.Name = "splitter1";
this.splitter1.Size = new System.Drawing.Size(3, 356);
this.splitter1.TabIndex = 5;
this.splitter1.TabStop = false;
//
// rtfBox
//
this.rtfBox.BorderStyle = System.Windows.Forms.Borde
this.rtfBox.Dock = System.Windows.Forms.DockS
this.rtfBox.Location = new System.Drawing.Point(463, 49);
this.rtfBox.Name = "rtfBox";
this.rtfBox.Size = new System.Drawing.Size(159, 96);
this.rtfBox.TabIndex = 6;
this.rtfBox.Text = "";
//
// splitter2
//
this.splitter2.Dock = System.Windows.Forms.DockS
this.splitter2.Location = new System.Drawing.Point(463, 145);
this.splitter2.Name = "splitter2";
this.splitter2.Size = new System.Drawing.Size(159, 3);
this.splitter2.TabIndex = 7;
this.splitter2.TabStop = false;
//
// lstPicture
//
this.lstPicture.BorderStyl
this.lstPicture.Dock = System.Windows.Forms.DockS
this.lstPicture.Location = new System.Drawing.Point(463, 148);
this.lstPicture.Name = "lstPicture";
this.lstPicture.Size = new System.Drawing.Size(159, 249);
this.lstPicture.TabIndex = 8;
//
// Viewer
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(622, 426);
this.Controls.Add(this.lst
this.Controls.Add(this.spl
this.Controls.Add(this.rtf
this.Controls.Add(this.spl
this.Controls.Add(this.Ima
this.Controls.Add(this.bar
this.Controls.Add(this.bar
this.Controls.Add(this.bar
this.Controls.Add(this.bar
this.Name = "Viewer";
this.Text = "Viewer";
this.Resize += new System.EventHandler(this.V
this.Load += new System.EventHandler(this.V
this.Layout += new System.Windows.Forms.Layou
this.Paint += new System.Windows.Forms.Paint
this.ResumeLayout(false);
}
#endregion
#region Menu Commands
private void menu_ItemClick(object sender, System.EventArgs e)
{
BaseItem item = sender as BaseItem;
switch(item.Name)
{
case "bOpen":
OpenPicture();
break;
case "bClose":
break;
case "bSave":
break;
case "bSaveAs":
break;
case "bBatch":
BatchDialog();
break;
case "bScrollable":
this.ImageControl.SetScrol
break;
case "bRatio":
this.ImageControl.SetRatio
break;
case "bOCRPage":
OCRPage();
break;
case "bOCRSelection":
OCRArea();
break;
case "bBatchSelection":
BatchSelection();
break;
case "bOCRPageThread":
OCRPageThreaded();
break;
case "bOCRSelectionThreaded":
OCRSelectionThreaded();
break;
}
}
#endregion
#region OCR Class For Threading
public class BatchProcess
{
#region Instance Variables ( for Batch Class )
ImageBatch iBatch;
ImageControl iControl;
ImageInfo Picture;
DotNetBarManager menu;
ListBox lstPicture;
RichTextBox rtfBox;
#endregion
#region BatchProcess Constructor
public BatchProcess(ImageBatch image,ImageControl control,
ImageInfo Pic,DotNetBarManager mnu,ListBox lst,RichTextBox rtf)
{
iBatch = image;
iControl = control;
Picture = Pic;
menu = mnu;
lstPicture = lst;
rtfBox = rtf;
}
#endregion
#region External Methods for Instantiating Batch Processing
public void ExecuteBatch()
{
BatchDialog();
}
public void ExecuteSelectionBatch()
{
BatchSelection();
}
#endregion
#region BatchDialog (Whole Page) Process
public void BatchDialog()
{
iBatch.Viewer = iControl;
BatchProcessor Batch = new BatchProcessor(iBatch);
Batch.ShowDialog();
#region Loop For the Batch Process
if(Batch.DialogResult != DialogResult.Cancel)
{
Int32 BatchLength = iBatch.Picture.Length - 1;
for(int i = 0; i <= BatchLength;i++)
{
API.TOCRInitialise(ref iBatch.Picture[i].JobNo);
iBatch.Picture[i].JobInfo.
OCRuser.TOCRJOBTYPE_DIBFIL
PerformOCR(iBatch.Picture[
}
#region Set all 'Picture' Values to Last Item of Batch
Picture = new ImageInfo();
Picture.Filename =
iBatch.Picture[BatchLength
Picture.Image =
iBatch.Picture[BatchLength
Picture.FileFormat =
iBatch.Picture[BatchLength
Picture.Viewer =
iBatch.Picture[BatchLength
#endregion
}
#endregion
}
#endregion
#region Batch Selection (For A Selected Area)
public void BatchSelection()
{
iBatch.Viewer = iControl;
BatchProcessor Batch = new BatchProcessor(iBatch);
Batch.ShowDialog();
if(Batch.DialogResult != DialogResult.Cancel)
{
Int32 BatchLength = iBatch.Picture.Length - 1;
#region Loop for Batch Process
for(int i = 0; i <= BatchLength;i++)
{
API.TOCRInitialise(ref iBatch.Picture[i].JobNo);
iBatch.Picture[i].Viewer = iControl;
Methods.HandleFile(iBatch.
iControl.OCRSelection(iBat
Methods.CreateSelectionMMF
iBatch.Picture[i].JobInfo.
OCRuser.TOCRJOBTYPE_DIBFIL
PerformOCR(iBatch.Picture[
}
#endregion
#region Set 'Picture' Object to Last Item of Batch
Picture = new ImageInfo();
Picture.Filename =
iBatch.Picture[BatchLength
Picture.Image =
iBatch.Picture[BatchLength
Picture.FileFormat =
iBatch.Picture[BatchLength
Picture.Viewer =
iBatch.Picture[BatchLength
#endregion
}
}
#endregion
#region OCR Method (Most Important!!)
private void PerformOCR(ImageInfo Picture)
{
LabelItem lblStatus = menu.GetItem("lblStatus") as LabelItem;
lblStatus.Text = "Performing Recognition";
Picture.JobStatus =
API.TOCRDoJob(Picture.JobN
ref Picture.JobInfo);
if ( API.TOCRGetJobStatus(Pictu
{
do
{
lblStatus.Text = "Fetching Job Status";
API.TOCRGetJobStatus
(Picture.JobNo,ref Picture.JobStatus);
}
while( Picture.JobStatus == 0);
}
else
{
lblStatus.Text = "Waiting For Job";
API.TOCRWaitForJob
(Picture.JobNo,ref Picture.JobStatus);
}
if ( Picture.JobStatus == OCRuser.TOCRJOBSTATUS_DONE
{
lblStatus.Text = "Job Done.Allocating Results";
Picture.ResultsHeaderSize =
Marshal.SizeOf(typeof(TOCR
lblStatus.Text = "Header Size: " +
Picture.ResultsHeaderSize.
lstPicture.Items.Add("Head
Picture.ResultsHeaderSize.
Picture.ResultsItemSize =
Marshal.SizeOf(typeof(TOCR
lblStatus.Text = "Item Size: " +
Picture.ResultsItemSize.To
lstPicture.Items.Add("Item
Picture.ResultsItemSize.To
API.TOCRGetJobResults(Pict
ref Picture.numberOfBytes, IntPtr.Zero);
Picture.ptAddress = IntPtr.Zero;
Picture.ptAddress =
Marshal.AllocHGlobal(Pictu
this.lstPicture.Items.Add(
Picture.ptAddress.ToString
API.TOCRGetJobResults(Pict
ref Picture.numberOfBytes, Picture.ptAddress);
TOCRResultsHeader hdr = (TOCRResultsHeader)Marshal
(Picture.ptAddress, typeof(TOCRResultsHeader))
Picture.numItems =
(Picture.numberOfBytes - Picture.ResultsHeaderSize)
Picture.ResultsItemSize;
lblStatus.Text = "Number of Items: " + Picture.numItems.ToString(
lstPicture.Items.Add("Numb
Picture.numItems.ToString(
Picture.ResultsItemAddress
Picture.ResultsHeaderSize)
TOCRResultsItem item = new TOCRResultsItem();
for(int i = 0;
i < Picture.numItems;
i++ )
{
Picture.AddressOfItem =
(IntPtr)((int)Picture.Resu
i * Picture.ResultsItemSize);
item =
(TOCRResultsItem)Marshal.P
(Picture.AddressOfItem,
typeof(TOCRResultsItem));
if( (char)item.OCRCha == '\r' )
{
Picture.strResults += "\r";
}
else
{
Picture.strResults += (char)item.OCRCha;
}
}
}
#region Finalize and Clear Everything
rtfBox.Text += Picture.strResults;
Picture.strResults = "";
rtfBox.Text += "\n<-- End of File -->\n";
Marshal.FreeHGlobal(Pictur
API.TOCRShutdown(Picture.J
#endregion
}
}
#endregion
#region Methods for Performing the OCR
private void OCRPageThreaded()
{
OCRThread = new Thread(new ThreadStart(new BatchProcess
(ImageBatch,this.ImageCont
this.menu,this.lstPicture,
OCRThread.Start();
}
private void OCRSelectionThreaded()
{
OCRThread = new Thread(new ThreadStart(new BatchProcess
(ImageBatch,this.ImageCont
this.menu,this.lstPicture,
OCRThread.Start();
}
#endregion
#endregion
#region Batch Commands
private void BatchDialog()
{
ImageBatch.Viewer = this.ImageControl;
BatchProcessor Batch = new BatchProcessor(ImageBatch)
Batch.ShowDialog();
if(Batch.DialogResult != DialogResult.Cancel)
{
Int32 BatchLength = ImageBatch.Picture.Length - 1;
for(int i = 0; i <= BatchLength;i++)
{
API.TOCRInitialise(ref ImageBatch.Picture[i].JobN
ImageBatch.Picture[i].JobI
OCRuser.TOCRJOBTYPE_DIBFIL
PerformOCR(ImageBatch.Pict
}
Picture = new ImageInfo();
Picture.Filename =
ImageBatch.Picture[BatchLe
Picture.Image =
ImageBatch.Picture[BatchLe
Picture.FileFormat =
ImageBatch.Picture[BatchLe
Picture.Viewer =
ImageBatch.Picture[BatchLe
}
}
private void BatchSelection()
{
ImageBatch.Viewer = this.ImageControl;
BatchProcessor Batch = new BatchProcessor(ImageBatch)
Batch.ShowDialog();
if(Batch.DialogResult != DialogResult.Cancel)
{
Int32 BatchLength = ImageBatch.Picture.Length - 1;
for(int i = 0; i <= BatchLength;i++)
{
API.TOCRInitialise(ref ImageBatch.Picture[i].JobN
ImageBatch.Picture[i].View
Methods.HandleFile(ImageBa
this.ImageControl.OCRSelec
Methods.CreateSelectionMMF
ImageBatch.Picture[i].JobI
OCRuser.TOCRJOBTYPE_DIBFIL
PerformOCR(ImageBatch.Pict
}
Picture = new ImageInfo();
Picture.Filename =
ImageBatch.Picture[BatchLe
Picture.Image =
ImageBatch.Picture[BatchLe
Picture.FileFormat =
ImageBatch.Picture[BatchLe
Picture.Viewer =
ImageBatch.Picture[BatchLe
}
}
#endregion
#region Image Methods
private void OpenPicture()
{
Picture.Filename =
this.ImageControl.OpenNewP
Methods.HandleFile(Picture
LabelItem lblFileType = menu.GetItem("lblFileType"
lblFileType.Text = Picture.FileFormat;
lstPicture.Items.Add("File
(Picture.Filename.LastInde
Picture.Filename.Length -
Picture.Filename.LastIndex
lstPicture.Items.Add("Form
}
#endregion
#region OCR Methods
private void OCRArea()
{
API.TOCRInitialise(ref Picture.JobNo);
this.ImageControl.OCRSelec
lstPicture.Items.Add("Sele
Picture.Selected.ToString(
Methods.CreateSelectionMMF
Picture.JobInfo.JobType = OCRuser.TOCRJOBTYPE_DIBFIL
PerformOCR(Picture);
}
private void OCRPage()
{
API.TOCRInitialise(ref Picture.JobNo);
Methods.CreateMMFile(Pictu
Picture.JobInfo.JobType = OCRuser.TOCRJOBTYPE_DIBFIL
PerformOCR(Picture);
}
private void PerformOCR(ImageInfo Picture)
{
LabelItem lblStatus = menu.GetItem("lblStatus") as LabelItem;
lblStatus.Text = "Performing Recognition";
Picture.JobStatus =
API.TOCRDoJob(Picture.JobN
ref Picture.JobInfo);
if ( API.TOCRGetJobStatus(Pictu
{
do
{
lblStatus.Text = "Fetching Job Status";
API.TOCRGetJobStatus
(Picture.JobNo,ref Picture.JobStatus);
}
while( Picture.JobStatus == 0);
}
else
{
lblStatus.Text = "Waiting For Job";
API.TOCRWaitForJob
(Picture.JobNo,ref Picture.JobStatus);
}
if ( Picture.JobStatus == OCRuser.TOCRJOBSTATUS_DONE
{
lblStatus.Text = "Job Done.Allocating Results";
Picture.ResultsHeaderSize =
Marshal.SizeOf(typeof(TOCR
lblStatus.Text = "Header Size: " +
Picture.ResultsHeaderSize.
lstPicture.Items.Add("Head
Picture.ResultsHeaderSize.
Picture.ResultsItemSize =
Marshal.SizeOf(typeof(TOCR
lblStatus.Text = "Item Size: " +
Picture.ResultsItemSize.To
lstPicture.Items.Add("Item
Picture.ResultsItemSize.To
API.TOCRGetJobResults(Pict
ref Picture.numberOfBytes, IntPtr.Zero);
Picture.ptAddress = IntPtr.Zero;
Picture.ptAddress =
Marshal.AllocHGlobal(Pictu
this.lstPicture.Items.Add(
Picture.ptAddress.ToString
API.TOCRGetJobResults(Pict
ref Picture.numberOfBytes, Picture.ptAddress);
TOCRResultsHeader hdr = (TOCRResultsHeader)Marshal
(Picture.ptAddress, typeof(TOCRResultsHeader))
Picture.numItems =
(Picture.numberOfBytes - Picture.ResultsHeaderSize)
Picture.ResultsItemSize;
lblStatus.Text = "Number of Items: " + Picture.numItems.ToString(
lstPicture.Items.Add("Numb
Picture.numItems.ToString(
Picture.ResultsItemAddress
Picture.ResultsHeaderSize)
TOCRResultsItem item = new TOCRResultsItem();
for(int i = 0;
i < Picture.numItems;
i++ )
{
Picture.AddressOfItem =
(IntPtr)((int)Picture.Resu
i * Picture.ResultsItemSize);
item =
(TOCRResultsItem)Marshal.P
(Picture.AddressOfItem,
typeof(TOCRResultsItem));
if( (char)item.OCRCha == '\r' )
{
Picture.strResults += "\r";
}
else
{
Picture.strResults += (char)item.OCRCha;
}
}
}
rtfBox.Text += Picture.strResults;
Picture.strResults = "";
rtfBox.Text += "\n<-- End of File -->\n";
Marshal.FreeHGlobal(Pictur
API.TOCRShutdown(Picture.J
}
#endregion
#region Event Handlers
private void Viewer_Load(object sender, System.EventArgs e)
{
this.ImageControl.Width = this.Width - 250;
this.rtfBox.Height = this.Height - 300;
Int32 PNo;
if(API.TOCRSetErrorMode(OC
OCRuser.TOCRERRORMODE_MSGB
this.Close();
this.Text = System.Reflection.Assembly
for(PNo = mIP;PNo < mRP;PNo++)
{
mPane[PNo].hDCMem = GDI.CreateCompatibleDC((in
mPane[PNo].BI.hBmp = 0;
mPane[PNo].AllowPaint = true;
mPane[PNo].Zoom = 100;
mPane[PNo].zLeft = 0;
mPane[PNo].zTop = 0;
mPane[PNo].zWidth = 0;
mPane[PNo].zHeight = 0;
}
Picture.JobInfo.StructId = 0;
this.Update();
}
private void Viewer_Paint(object sender, System.Windows.Forms.Paint
{
this.ImageControl.Width = this.Width - 250;
this.rtfBox.Height = this.Height - 300;
}
private void Viewer_Resize(object sender, System.EventArgs e)
{
this.ImageControl.Width = this.Width - 250;
this.rtfBox.Height = this.Height - 300;
}
private void Viewer_Layout(object sender, System.Windows.Forms.Layou
{
this.ImageControl.Width = this.Width - 250;
this.rtfBox.Height = this.Height - 300;
}
public struct BMPINFO
{
public int hBmp;
public int Width;
public int Height;
public int XPelsPerMeter;
public int YPelsPerMeter;
public int Length;
}
public struct PANEINFO
{
public BMPINFO BI;
public Int32 hDCMem;
public Int32 Zoom;
public Int32 zLeft;
public Int32 zTop;
public Int32 zWidth;
public Int32 zHeight;
public bool AllowPaint;
}
#endregion
}// Viewer Class
}