I have (painfully) written a stable console control, but am having a tiny issue regarding when starting the object the Caret is not being set to my custom one until I click inside the control on the form or push a key on the keyboard. I can see that I have focus successfully as the default (very faint line) Caret shows up blinking with no problems, even in spite of me constantly trying to destroy the little bugger. (sorry for condensing some of the code. SO limits to 30,000 characters)
Here is my source code :
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
public partial class Console : RichTextBox {
public Console() {
InitializeComponent();
this.ShowDebug=false;
this.ShowErrors=true;
process=new Process();
this.WorkingDirectory=Environment.GetFolderPath( Environment.SpecialFolder.MyDocuments );
this.stripCommand="";
startinfo=new ProcessStartInfo() {
RedirectStandardError=true,
RedirectStandardInput=true,
RedirectStandardOutput=true,
StandardErrorEncoding=System.Text.Encoding.GetEncoding( 437 ),
StandardOutputEncoding=System.Text.Encoding.GetEncoding( 437 ),
UseShellExecute=false,
ErrorDialog=false,
CreateNoWindow=true,
WorkingDirectory=this.WorkingDirectory
//WindowStyle=System.Diagnostics.ProcessWindowStyle.Normal
};
this.inputStart=0;
base.BackColor=Color.DimGray;
base.ForeColor=Color.GhostWhite;
base.AcceptsTab=true;
base.ReadOnly=true;
this.AllowCommands = true;
this.CaretVisible = true;
base.Anchor=AnchorStyles.Bottom|AnchorStyles.Left|AnchorStyles.Right|AnchorStyles.Top;
base.Font=new Font( "Lucida Console", (float)11.25 );
using ( Graphics g=base.CreateGraphics() ) {
base.Size=new Size( Convert.ToInt32( g.MeasureString( "A", base.Font ).Width*80 ), Convert.ToInt32( g.MeasureString( "A", base.Font ).Height*25 ) );
}
this.ErrorColor=Color.LightPink;
this.DebugColor=Color.Yellow;
// Configure the output worker.
outputWorker.WorkerReportsProgress=true;
outputWorker.WorkerSupportsCancellation=true;
outputWorker.DoWork+=outputWorker_DoWork;
outputWorker.ProgressChanged+=outputWorker_ProgressChanged;
outputWorker.RunWorkerCompleted+=outputWorker_RunWorkerCompleted;
// Configure the error worker.
errorWorker.WorkerReportsProgress=true;
errorWorker.WorkerSupportsCancellation=true;
errorWorker.DoWork+=errorWorker_DoWork;
errorWorker.ProgressChanged+=errorWorker_ProgressChanged;
errorWorker.RunWorkerCompleted+=errorWorker_RunWorkerCompleted;
// Map 'tab'.
keyMappings.Add( new KeyMapping( false, false, false, Keys.Tab, "{TAB}", "\t" ) );
// Map 'Ctrl-C'.
keyMappings.Add( new KeyMapping( true, false, false, Keys.C, "^(c)", "\x03\r\n" ) );
}
#region methods
/// <summary>
/// Change the output encoding
/// </summary>
/// <param name="encoding"><see cref="System.Text.Encoding"/></param>
public void SetOutputEncoding( System.Text.Encoding encoding ) {
if ( Running&&ShowDebug ) {
Write( "Encoding change will take effect when restarting the process", DebugColor );
}
startinfo.StandardErrorEncoding=encoding;
startinfo.StandardOutputEncoding=encoding;
}
public async void SendCommand( string value ) {
if ( Running==false&&ShowErrors ) {
Write( startinfo.FileName+"` is not running. Command `"+value+"` failed", DebugColor );
return;
}
stripCommand=value;
await inputWriter.WriteLineAsync( value );
inputWriter.Flush();
}
public void Write( string value, Color color ) {
// Ensure that text is purged from the top of the textbox
// if the amount of text in the box is approaching the
// MaxLength property of the control
if ( base.Text.Length+value.Length>base.MaxLength ) {
int cr=base.Text.IndexOf( "\r\n" );
if ( cr>0 ) {
base.Select( 0, cr+1 );
base.SelectedText=string.Empty;
} else {
base.Select( 0, value.Length );
}
}
string v="";
base.SelectionColor=color;
if ( color==ErrorColor ) {
v="- ERROR: ";
} else if ( color==DebugColor ) {
v="- DEBUG: ";
}
if ( v.Length>0 ) {
if ( value.Trim()=="" ) { return; }
if ( base.Text.EndsWith( "\r\n" )==false&&base.Text.EndsWith( "\n" )==false&&base.Text.Length>0 ) { v="\n"+v; }
base.SelectionFont=new Font( base.Font.FontFamily, base.Font.Size, FontStyle.Bold );
base.SelectedText+=v;
base.SelectionFont=new Font( base.Font.FontFamily, base.Font.Size, FontStyle.Regular );
value+="\n";
}
base.SelectedText+=value;
inputStart=base.SelectionStart;
base.SelectionStart=base.Text.Length+1;
base.ScrollToCaret();
base.SelectionStart=base.Text.Length+1;
if ( AllowCommands==true ) {
DrawCaret();
if ( this.Focused==false ) {
this.Focus();
}
}
}
/// <summary>
/// Start a process
/// </summary>
/// <param name="command">The command to run ( example: "cmd.exe" )</param>
/// <param name="arguments">Any arguments you wish to pass with the command</param>
public void Start( string command, string arguments="" ) {
if ( Running&&ShowDebug ) {
Write( "A process is already running."+startinfo.FileName+"` started", ErrorColor );
return;
}
if ( ThreadsStillRunning&&ShowDebug ) {
Write( "The process has ended, but threads are still pending cancellation. Try again in a few moments", ErrorColor );
return;
}
startinfo.WorkingDirectory = this.WorkingDirectory;
startinfo.Arguments=arguments;
startinfo.FileName=command;
process=new Process {
StartInfo=startinfo
};
process.EnableRaisingEvents=true;
process.Exited+=process_Exited;
// Start the process.
try {
base.SelectionStart = base.TextLength;
process.Start();
DestroyCaret();
if ( AllowCommands ) {
base.ReadOnly = false;
DrawCaret();
if ( this.Focused==false ) {
this.Focus();
}
}
if ( base.Text.Length>0&&base.Text.EndsWith( "\n" )==false ) {
base.AppendText("\n");
base.SelectionStart=base.TextLength;
}
if ( Running&&ShowDebug ) {
Write( startinfo.FileName+"` started", DebugColor );
}
} catch ( Exception e ) {
// Trace the exception.
string error=( arguments=="" )?"Failed to start process "+command:"Failed to start process "+command+" with arguments '"+arguments+"'";
Trace.WriteLine( "ERROR: "+error );
Trace.WriteLine( e.ToString() );
if ( ShowErrors ) {
Write( error, ErrorColor );
}
return;
}
// Create the readers and writers.
inputWriter=process.StandardInput;
outputReader=TextReader.Synchronized( process.StandardOutput );
errorReader=TextReader.Synchronized( process.StandardError );
// Run the workers that read output and error.
if ( outputWorker.IsBusy==false ) { outputWorker.RunWorkerAsync(); }
if ( errorWorker.IsBusy==false ) { errorWorker.RunWorkerAsync(); }
}
/// <summary>
/// Stop the current process
/// </summary>
public void Stop() {
if ( ShowDebug ) {
Write( "Requesting termination of command", DebugColor );
}
// Handle the trivial case.
if ( Running==false ) {
if ( ShowErrors ) {
Write( "Process is not running. Terminate failed.", DebugColor );
}
return;
}
// Kill the process.
process.Kill();
process.WaitForExit();
}
#endregion
#region settings
/// <summary>
/// Set the console background color
/// </summary>
[DefaultValue( typeof( Color ), "DimGray" )]
public override Color BackColor {
get {
return base.BackColor;
}
set {
base.BackColor=value;
}
}
/// <summary>
/// Set the console font color
/// </summary>
[DefaultValue( typeof( Color ), "GhostWhite" )]
public override Color ForeColor {
get {
return base.ForeColor;
}
set {
base.ForeColor=value;
}
}
/// <summary>
/// Toggle visibility of debug messages
/// </summary>
[DefaultValue( false )]
public bool ShowDebug { get; set; }
/// <summary>
/// Toggle visibility of error messages
/// </summary>
[DefaultValue( true )]
public bool ShowErrors { get; set; }
/// <summary>
/// Send commands from the console to be processed. ( Does not impact SendCommand() )
/// </summary>
[DefaultValue( true )]
public bool AllowCommands {
get { return pAllowCommands; }
set {
pAllowCommands=value;
if ( value==false ) {
DestroyCaret();
}
}
}
private bool pAllowCommands;
/// <summary>
/// Set the color for debug messages
/// </summary>
[DefaultValue( typeof( Color ), "Yellow" )]
public Color DebugColor { get; set; }
/// <summary>
/// Set the color for error messages
/// </summary>
[DefaultValue( typeof( Color ), "LightPink" )]
public Color ErrorColor { get; set; }
/// <summary>
/// Returns true if process is running
/// </summary>
public bool Running {
get {
try {
return ( process!=null&&process.HasExited==false );
} catch {
return false;
}
}
}
/// <summary>
/// Returns if stream threads are still running
/// </summary>
public bool ThreadsStillRunning {
get {
if ( errorWorker.IsBusy||outputWorker.IsBusy ) {
return true;
} else {
return false;
}
}
}
/// <summary>
/// Working directory to use for startup
/// </summary>
public string WorkingDirectory { get; set; }
#endregion
#region carat
[DllImport( "user32.dll", CharSet=CharSet.Auto )]
private extern static void CreateCaret( IntPtr hWnd, IntPtr hBitmap, int nWidth, int nHeight );
[DllImport( "user32.dll", CharSet=CharSet.Auto )]
private extern static void ShowCaret( IntPtr hWnd );
[DllImport( "user32.dll" )]
static extern bool DestroyCaret();
[DllImport( "user32.dll" )]
static extern bool SetCaretPos( int X, int Y );
[DllImport( "user32.dll" )]
static extern bool HideCaret( IntPtr hWnd );
protected override void OnKeyUp( KeyEventArgs e ) {
DrawCaret();
base.OnKeyUp( e );
}
/// <summary>
/// Set the Caret color (not implemented yet)
/// </summary>
public Color CaretColor { get; set; }
/// <summary>
/// Set the Caret size (not implemented yet)
/// </summary>
public Size CaretSize { get; set; }
/// <summary>
/// Define if the Caret should be visible or not
/// </summary>
[DefaultValue( true )]
public bool CaretVisible { get; set; }
/// <summary>
/// Draw the caret
/// </summary>
internal void DrawCaret() {
if ( base.ReadOnly==true||AllowCommands==false||CaretVisible==false ) { DestroyCaret(); return; }
int nHeight=0;
int nWidth=5;
bool mInsertKeyState=false;
if ( this.SelectionFont!=null ) {
nHeight=Convert.ToInt32( base.SelectionFont.Height*base.ZoomFactor );
} else {
nHeight=Convert.ToInt32( ( base.Font.Height*base.ZoomFactor/3 ) );
}
if ( !mInsertKeyState&&base.SelectionStart<base.TextLength ) {
Point p1=base.GetPositionFromCharIndex( base.SelectionStart );
Point p2=base.GetPositionFromCharIndex( base.SelectionStart+1 );
nWidth=p2.X-p1.X;
}
Color invertedCaretColor=Color.FromArgb( CaretColor.ToArgb()^0xffffff );
if ( nWidth>0&&nHeight>0 ) {
using ( Bitmap bmp=new Bitmap( nWidth, nHeight ) ) {
using ( Graphics gfx=Graphics.FromImage( bmp ) ) {
nWidth=Convert.ToInt32( gfx.MeasureString( "Z", base.Font ).Width*.66 );
//nHeight=Convert.ToInt32( gfx.MeasureString( "Z", base.Font ).Height / 3 );
using ( SolidBrush brush=new SolidBrush( invertedCaretColor ) ) {
gfx.FillRectangle( brush, 1, 1, nWidth, nHeight );
using ( Bitmap bmp2=new Bitmap( nWidth, nHeight, gfx ) ) {
CreateCaret( base.Handle, bmp2.GetHbitmap(), nWidth, nHeight );
ShowCaret( base.Handle );
}
}
}
}
}
}
/// <summary>
/// internal method to wait for thread to exit
/// </summary>
/// <returns></returns>
private async Task<bool> waitOnThreadExit() {
await System.Threading.Tasks.Task.Run( () => {
while ( ThreadsStillRunning==true ) {
System.Threading.Thread.Sleep( 500 );
}
} );
return true;
}
#endregion
#region userprocessing
/// <summary>
/// Handle when this object recieves focus
/// </summary>
/// <param name="e"></param>
protected override void OnGotFocus( EventArgs e ) {
if ( AllowCommands==true ) {
base.SelectionStart=base.TextLength;
DrawCaret();
} else {
DestroyCaret();
}
base.OnGotFocus( e );
}
/// <summary>
/// Handle keypress (keydown) event
/// </summary>
/// <param name="e"></param>
protected override void OnKeyDown( KeyEventArgs e ) {
if ( base.ReadOnly ) {
e.SuppressKeyPress=true;
base.OnKeyDown( e );
return;
}
if ( AllowCommands&&Running&&base.ReadOnly==false ) {
// Get key mappings for this key event?
var mappings=from k in keyMappings
where
( k.KeyCode==e.KeyCode&&
k.IsAltPressed==e.Alt&&
k.IsControlPressed==e.Control&&
k.IsShiftPressed==e.Shift )
select k;
// Go through each mapping, send the message.
foreach ( var mapping in mappings ) {
//SendKeysEx.SendKeys(CurrentProcessHwnd, mapping.SendKeysMapping);
//inputWriter.WriteLine(mapping.StreamMapping);
//WriteInput("\x3", Color.White, false);
}
// If we handled a mapping, we're done here.
if ( mappings.Any() ) {
e.SuppressKeyPress=true;
base.OnKeyDown( e );
return;
}
}
// If we're at the input point and it's backspace, bail.
if ( ( base.SelectionStart<=inputStart )&&e.KeyCode==Keys.Back )
e.SuppressKeyPress=true;
// Are we in the read-only zone?
if ( base.SelectionStart<inputStart ) {
// Allow arrows and Ctrl-C.
if ( !( e.KeyCode==Keys.Left||
e.KeyCode==Keys.Right||
e.KeyCode==Keys.Up||
e.KeyCode==Keys.Down||
( e.KeyCode==Keys.C&&e.Control ) ) ) {
e.SuppressKeyPress=true;
}
}
// Is it the return key?
if ( e.KeyCode==Keys.Return&&base.ReadOnly==false&&AllowCommands==true ) {
// Get the input.
try {
string input=base.Text.Substring( inputStart, ( base.SelectionStart )-inputStart );
stripCommand=input;
Invoke( (Action)( () => {
SendCommand( input );
} ) );
} catch ( Exception ex ) {
} finally {
DrawCaret();
}
} else if ( base.ReadOnly==true||AllowCommands==false ) {
e.SuppressKeyPress=true;
}
base.OnKeyDown( e );
}
/// <summary>
/// handle mouse up event
/// </summary>
/// <param name="mevent"></param>
protected override void OnMouseUp( MouseEventArgs mevent ) {
if ( AllowCommands==true ) {
DrawCaret();
} else {
DestroyCaret();
}
base.OnMouseUp( mevent );
}
/// <summary>
/// store current input start location. used to deny editing of any prior data.
/// </summary>
private int inputStart { get; set; }
/// <summary>
/// store last command to strip from incoming data so it doesn't end up being displayed twice
/// </summary>
private string stripCommand { get; set; }
public delegate void ConsoleEventHandler( object sender, ConsoleEventArgs args );
/// <summary>
/// The ProcessEventArgs are arguments for a console event.
/// </summary>
public class ConsoleEventArgs : EventArgs {
/// <summary>
/// Initializes a new instance of the <see cref="ProcessEventArgs"/> class.
/// </summary>
public ConsoleEventArgs() {
}
/// <summary>
/// Initializes a new instance of the <see cref="ProcessEventArgs"/> class.
/// </summary>
/// <param name="content">The content.</param>
public ConsoleEventArgs( string content ) {
// Set the content and code.
Content=content;
}
/// <summary>
/// Initializes a new instance of the <see cref="ProcessEventArgs"/> class.
/// </summary>
/// <param name="code">The code.</param>
public ConsoleEventArgs( int code ) {
// Set the content and code.
Code=code;
}
/// <summary>
/// Initializes a new instance of the <see cref="ProcessEventArgs"/> class.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="code">The code.</param>
public ConsoleEventArgs( string content, int code ) {
// Set the content and code.
Content=content;
Code=code;
}
/// <summary>
/// Gets the content.
/// </summary>
public string Content { get; private set; }
/// <summary>
/// Gets or sets the code.
/// </summary>
/// <value>
/// The code.
/// </value>
public int? Code { get; private set; }
}
/// <summary>
/// The type of signal to be generated.
/// </summary>
internal enum CTRL_EVENT : uint {
/// <summary>
/// Generates a CTRL+C signal. This signal cannot be generated for process groups. If dwProcessGroupId is nonzero, this function will succeed, but the CTRL+C signal will not be received by processes within the specified process group.
/// </summary>
CTRL_C_EVENT=0,
/// <summary>
/// Generates a CTRL+BREAK signal.
/// </summary>
CTRL_BREAK_EVENT=1
}
/// <summary>
/// The key mappings.
/// </summary>
private List<KeyMapping> keyMappings=new List<KeyMapping>();
/// <summary>
/// Keyboard handling imports
/// </summary>
internal static class Imports {
/// <summary>
/// Sends a specified signal to a console process group that shares the console associated with the calling process.
/// </summary>
/// <param name="dwCtrlEvent">The type of signal to be generated.</param>
/// <param name="dwProcessGroupId">The identifier of the process group to receive the signal. A process group is created when the CREATE_NEW_PROCESS_GROUP flag is specified in a call to the CreateProcess function. The process identifier of the new process is also the process group identifier of a new process group. The process group includes all processes that are descendants of the root process. Only those processes in the group that share the same console as the calling process receive the signal. In other words, if a process in the group creates a new console, that process does not receive the signal, nor do its descendants.
/// If this parameter is zero, the signal is generated in all processes that share the console of the calling process.</param>
/// <returns>If the function succeeds, the return value is nonzero.
/// If the function fails, the return value is zero. To get extended error information, call GetLastError.</returns>
[DllImport( "Kernel32.dll" )]
public static extern bool GenerateConsoleCtrlEvent( CTRL_EVENT dwCtrlEvent, UInt32 dwProcessGroupId );
}
// Internal KeyMapping Class
/// <summary>
/// A KeyMapping defines how a key combination should
/// be mapped to a SendKeys message.
/// </summary>
internal class KeyMapping {
/// <summary>
/// Initializes a new instance of the <see cref="KeyMapping"/> class.
/// </summary>
public KeyMapping() {
}
/// <summary>
/// Initializes a new instance of the <see cref="KeyMapping"/> class.
/// </summary>
/// <param name="control">if set to <c>true</c> [control].</param>
/// <param name="alt">if set to <c>true</c> [alt].</param>
/// <param name="shift">if set to <c>true</c> [shift].</param>
/// <param name="keyCode">The key code.</param>
/// <param name="sendKeysMapping">The send keys mapping.</param>
/// <param name="streamMapping">The stream mapping.</param>
public KeyMapping( bool control, bool alt, bool shift, Keys keyCode, string sendKeysMapping, string streamMapping ) {
// Set the member variables.
IsControlPressed=control;
IsAltPressed=alt;
IsShiftPressed=shift;
KeyCode=keyCode;
SendKeysMapping=sendKeysMapping;
StreamMapping=streamMapping;
}
/// <summary>
/// Gets or sets a value indicating whether this instance is control pressed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is control pressed; otherwise, <c>false</c>.
/// </value>
public bool IsControlPressed {
get;
set;
}
/// <summary>
/// Gets or sets a value indicating whether alt is pressed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is alt pressed; otherwise, <c>false</c>.
/// </value>
public bool IsAltPressed {
get;
set;
}
/// <summary>
/// Gets or sets a value indicating whether this instance is shift pressed.
/// </summary>
/// <value>
/// <c>true</c> if this instance is shift pressed; otherwise, <c>false</c>.
/// </value>
public bool IsShiftPressed {
get;
set;
}
/// <summary>
/// Gets or sets the key code.
/// </summary>
/// <value>
/// The key code.
/// </value>
public Keys KeyCode {
get;
set;
}
/// <summary>
/// Gets or sets the send keys mapping.
/// </summary>
/// <value>
/// The send keys mapping.
/// </value>
public string SendKeysMapping {
get;
set;
}
/// <summary>
/// Gets or sets the stream mapping.
/// </summary>
/// <value>
/// The stream mapping.
/// </value>
public string StreamMapping {
get;
set;
}
}
#endregion
/***********************************************************************
* THREAD HANDLING
* ********************************************************************/
#region thread
/// <summary>
/// Occurs when process output is produced.
/// </summary>
public event ConsoleEventHandler OnProcessOutput;
/// <summary>
/// Occurs when process error output is produced.
/// </summary>
public event ConsoleEventHandler OnProcessError;
/// <summary>
/// Occurs when process input is produced.
/// </summary>
public event ConsoleEventHandler OnProcessInput;
/// <summary>
/// Occurs when the process ends.
/// </summary>
public event ConsoleEventHandler OnProcessExit;
/// <summary>
/// The input writer.
/// </summary>
private StreamWriter inputWriter;
/// <summary>
/// The output reader.
/// </summary>
private TextReader outputReader;
/// <summary>
/// The error reader.
/// </summary>
private TextReader errorReader;
/// <summary>
/// The output worker.
/// </summary>
private BackgroundWorker outputWorker=new BackgroundWorker();
/// <summary>
/// The error worker.
/// </summary>
private BackgroundWorker errorWorker=new BackgroundWorker();
/// <summary>
/// property to hold System.Diagnostics.Process instance
/// </summary>
private Process process { get; set; }
/// <summary>
/// property to hold System.Diagnostics.ProcessStartInfo for initializing process
/// </summary>
private ProcessStartInfo startinfo { get; set; }
/// <summary>
/// Handles the ProgressChanged event of the outputWorker control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.ComponentModel.ProgressChangedEventArgs"/> instance containing the event data.</param>
void outputWorker_ProgressChanged( object sender, ProgressChangedEventArgs e ) {
// We must be passed a string in the user state.
if ( e.UserState is string ) {
// Fire the output event.
FireProcessOutputEvent( e.UserState as string );
}
}
/// <summary>
/// Handles the DoWork event of the outputWorker control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.ComponentModel.DoWorkEventArgs"/> instance containing the event data.</param>
void outputWorker_DoWork( object sender, DoWorkEventArgs e ) {
while ( outputWorker.CancellationPending==false ) {
// Any lines to read?
if ( outputWorker.CancellationPending ) { return; }
int count;
var buffer=new char[1024];
do {
if ( outputWorker.CancellationPending ) { return; }
var builder=new StringBuilder();
count=outputReader.Read( buffer, 0, 1024 );
builder.Append( buffer, 0, count );
if ( stripCommand.Length>0 ) {
if ( builder.ToString().StartsWith( stripCommand ) ) {
builder.Remove( 0, stripCommand.Length );
stripCommand="";
}
}
outputWorker.ReportProgress( 0, builder.ToString() );
} while ( count>0 );
System.Threading.Thread.Sleep( 200 );
}
}
/// <summary>
/// Notification of outputWorker completion
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void outputWorker_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) {
if ( ShowDebug ) {
Write( "Output Thread Cancellation Completed.", DebugColor );
}
}
/// <summary>
/// Handles the ProgressChanged event of the errorWorker control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.ComponentModel.ProgressChangedEventArgs"/> instance containing the event data.</param>
void errorWorker_ProgressChanged( object sender, ProgressChangedEventArgs e ) {
// The userstate must be a string.
if ( e.UserState is string ) {
// Fire the error event.
FireProcessErrorEvent( e.UserState as string );
}
}
/// <summary>
/// Notification of errorWorker completion
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void errorWorker_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) {
if ( ShowDebug ) {
Write( "Error Thread Cancellation Completed.", DebugColor );
}
}
/// <summary>
/// Handles the DoWork event of the errorWorker control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="System.ComponentModel.DoWorkEventArgs"/> instance containing the event data.</param>
void errorWorker_DoWork( object sender, DoWorkEventArgs e ) {
while ( errorWorker.CancellationPending==false ) {
// Any lines to read?
int count;
var buffer=new char[1024];
do {
var builder=new StringBuilder();
count=errorReader.Read( buffer, 0, 1024 );
builder.Append( buffer, 0, count );
errorWorker.ReportProgress( 0, builder.ToString() );
} while ( count>0 );
System.Threading.Thread.Sleep( 200 );
}
}
/// <summary>
/// Fires the process output event.
/// </summary>
/// <param name="content">The content.</param>
private void FireProcessOutputEvent( string content ) {
// Get the event and fire it.
var theEvent=OnProcessOutput;
Write( content, ForeColor );
if ( theEvent!=null )
theEvent( this, new ConsoleEventArgs( content ) );
}
/// <summary>
/// Fires the process error output event.
/// </summary>
/// <param name="content">The content.</param>
private void FireProcessErrorEvent( string content ) {
// Get the event and fire it.
var theEvent=OnProcessError;
if ( ShowErrors ) {
Write( content, ErrorColor );
}
if ( theEvent!=null )
theEvent( this, new ConsoleEventArgs( content ) );
}
/// <summary>
/// Fires the process input event.
/// </summary>
/// <param name="content">The content.</param>
private void FireProcessInputEvent( string content ) {
// Get the event and fire it.
var theEvent=OnProcessInput;
if ( theEvent!=null )
theEvent( this, new ConsoleEventArgs( content ) );
}
/// <summary>
/// Fires the process exit event.
/// </summary>
/// <param name="code">The code.</param>
private void FireProcessExitEvent( int code ) {
// Get the event and fire it.
var theEvent=OnProcessExit;
if ( ShowDebug ) {
Invoke( (Action)( () => {
Write( startinfo.FileName+" terminated", DebugColor );
} ) );
}
if ( theEvent!=null )
theEvent( this, new ConsoleEventArgs( code ) );
}
/// <summary>
/// Cleanup threads after process has exited.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
async void process_Exited( object sender, EventArgs e ) {
Invoke( (Action)( () => {
base.ReadOnly=true;
} ) );
try {
// Disable the threads.
outputWorker.CancelAsync();
errorWorker.CancelAsync();
await waitOnThreadExit();
} catch ( Exception ex ) {
} finally {
// Fire process exited.
FireProcessExitEvent( process.ExitCode );
// Disable the threads.
inputWriter=null;
outputReader=null;
errorReader=null;
process=null;
}
}
#endregion
}
Here is a picture of the problem on init :
Should look like :
Related
Below is the standard d3drenderer.cs file coding:
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Threading;
using System.Xml.Linq;
namespace WPFMediaKit.DirectShow.Controls
{
/// <summary>
/// The D3DRenderer class provides basic functionality needed
/// to render a D3D surface. This class is abstract.
/// </summary>
public abstract class D3DRenderer : FrameworkElement
{
private string workingDirectory = AppDomain.CurrentDomain.BaseDirectory;
/// <summary>
/// The D3DImage used to render video
/// </summary>
private D3DImage m_d3dImage;
/// <summary>
/// The Image control that has the source
/// to the D3DImage
/// </summary>
private Image m_videoImage;
/// <summary>
/// We keep reference to the D3D surface so
/// we can delay loading it to avoid a black flicker
/// when loading new media
/// </summary>
private IntPtr m_pBackBuffer = IntPtr.Zero;
/// <summary>
/// Flag to tell us if we have a new D3D
/// Surface available
/// </summary>
private bool m_newSurfaceAvailable;
/// <summary>
/// A weak reference of D3DRenderers that have been cloned
/// </summary>
private readonly List<WeakReference> m_clonedD3Drenderers = new List<WeakReference>();
/// <summary>
/// Backing field for the RenderOnCompositionTargetRendering flag.
/// </summary>
private bool m_renderOnCompositionTargetRendering;
/// <summary>
/// Temporary storage for the RenderOnCompositionTargetRendering flag.
/// This is used to remember the value for when the control is loaded and unloaded.
/// </summary>
private bool m_renderOnCompositionTargetRenderingTemp;
#region Stretch
public static readonly DependencyProperty StretchProperty =
DependencyProperty.Register("Stretch", typeof(Stretch), typeof(D3DRenderer),
new FrameworkPropertyMetadata(Stretch.Uniform,
new PropertyChangedCallback(OnStretchChanged)));
/// <summary>
/// Defines what rules are applied to the stretching of the video
/// </summary>
public Stretch Stretch
{
get { return (Stretch)GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
private static void OnStretchChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((D3DRenderer)d).OnStretchChanged(e);
}
private void OnStretchChanged(DependencyPropertyChangedEventArgs e)
{
m_videoImage.Stretch = (Stretch) e.NewValue;
}
#endregion
#region IsRenderingEnabled
public static readonly DependencyProperty IsRenderingEnabledProperty =
DependencyProperty.Register("IsRenderingEnabled", typeof(bool), typeof(D3DRenderer),
new FrameworkPropertyMetadata(true));
/// <summary>
/// Enables or disables rendering of the video
/// </summary>
public bool IsRenderingEnabled
{
get { return (bool)GetValue(IsRenderingEnabledProperty); }
set { SetValue(IsRenderingEnabledProperty, value); }
}
#endregion
#region NaturalVideoHeight
private static readonly DependencyPropertyKey NaturalVideoHeightPropertyKey
= DependencyProperty.RegisterReadOnly("NaturalVideoHeight", typeof(int), typeof(MediaElementBase),
new FrameworkPropertyMetadata(0));
public static readonly DependencyProperty NaturalVideoHeightProperty
= NaturalVideoHeightPropertyKey.DependencyProperty;
/// <summary>
/// Gets the natural pixel height of the current media.
/// The value will be 0 if there is no video in the media.
/// </summary>
public int NaturalVideoHeight
{
get { return (int)GetValue(NaturalVideoHeightProperty); }
}
/// <summary>
/// Internal method to set the read-only NaturalVideoHeight DP
/// </summary>
protected void SetNaturalVideoHeight(int value)
{
SetValue(NaturalVideoHeightPropertyKey, value);
}
#endregion
#region NaturalVideoWidth
private static readonly DependencyPropertyKey NaturalVideoWidthPropertyKey
= DependencyProperty.RegisterReadOnly("NaturalVideoWidth", typeof(int), typeof(MediaElementBase),
new FrameworkPropertyMetadata(0));
public static readonly DependencyProperty NaturalVideoWidthProperty
= NaturalVideoWidthPropertyKey.DependencyProperty;
/// <summary>
/// Gets the natural pixel width of the current media.
/// The value will be 0 if there is no video in the media.
/// </summary>
public int NaturalVideoWidth
{
get { return (int)GetValue(NaturalVideoWidthProperty); }
}
/// <summary>
/// Internal method to set the read-only NaturalVideoWidth DP
/// </summary>
protected void SetNaturalVideoWidth(int value)
{
SetValue(NaturalVideoWidthPropertyKey, value);
}
#endregion
#region HasVideo
private static readonly DependencyPropertyKey HasVideoPropertyKey
= DependencyProperty.RegisterReadOnly("HasVideo", typeof(bool), typeof(MediaElementBase),
new FrameworkPropertyMetadata(false));
public static readonly DependencyProperty HasVideoProperty
= HasVideoPropertyKey.DependencyProperty;
/// <summary>
/// Is true if the media contains renderable video
/// </summary>
public bool HasVideo
{
get { return (bool)GetValue(HasVideoProperty); }
}
/// <summary>
/// Internal method for setting the read-only HasVideo DP
/// </summary>
protected void SetHasVideo(bool value)
{
SetValue(HasVideoPropertyKey, value);
}
#endregion
protected D3DRenderer()
{
InitializeD3DVideo();
/* Hook into the framework events */
Loaded += D3DRenderer_Loaded;
Unloaded += D3DRenderer_Unloaded;
}
/// <summary>
/// Handler for when the D3DRenderer is unloaded
/// </summary>
private void D3DRenderer_Unloaded(object sender, RoutedEventArgs e)
{
/* Remember what the property value was */
m_renderOnCompositionTargetRenderingTemp = RenderOnCompositionTargetRendering;
/* Make sure to unhook the static event hook because we are unloading */
RenderOnCompositionTargetRendering = false;
}
/// <summary>
/// Handler for when the D3DRenderer is loaded
/// </summary>
private void D3DRenderer_Loaded(object sender, RoutedEventArgs e)
{
/* Restore the property's value */
RenderOnCompositionTargetRendering = m_renderOnCompositionTargetRenderingTemp;
}
/// <summary>
/// Initializes the D3DRenderer control
/// </summary>
protected virtual void InitializeD3DVideo()
{
if (m_videoImage != null)
return;
/* Create our Image and it's D3DImage source */
m_videoImage = new Image();
m_d3dImage = new D3DImage();
/* We hook into this event to handle when a D3D device is lost */
D3DImage.IsFrontBufferAvailableChanged += D3DImage_IsFrontBufferAvailableChanged;
/* Set our default stretch value of our video */
m_videoImage.Stretch = (Stretch)StretchProperty.DefaultMetadata.DefaultValue;
var activeProfiles = XElement.Load(workingDirectory + "ProfilesDisplay.xml");
var a = activeProfiles.Element("_nABC").Value;
/* Our source of the video image is the D3DImage */
m_videoImage.Source = D3DImage;
/* Register the Image as a visual child */
AddVisualChild(m_videoImage);
}
/// <summary>
/// This should only fire when a D3D device is lost
/// </summary>
private void D3DImage_IsFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (!D3DImage.IsFrontBufferAvailable)
return;
/* Flag that we have a new surface, even
* though we really don't */
m_newSurfaceAvailable = true;
/* Force feed the D3DImage the Surface pointer */
SetBackBufferInternal(m_pBackBuffer);
}
protected override Size MeasureOverride(Size availableSize)
{
m_videoImage.Measure(availableSize);
return m_videoImage.DesiredSize;
}
protected override Size ArrangeOverride(Size finalSize)
{
m_videoImage.Arrange(new Rect(finalSize));
return finalSize;
}
protected override int VisualChildrenCount
{
get
{
return 1;
}
}
protected override Visual GetVisualChild(int index)
{
if (index > 0)
throw new IndexOutOfRangeException();
return m_videoImage;
}
protected D3DImage D3DImage
{
get
{
return m_d3dImage;
}
}
protected Image VideoImage
{
get
{
return m_videoImage;
}
}
/// <summary>
/// Renders the video with WPF's rendering using the CompositionTarget.Rendering event
/// </summary>
protected bool RenderOnCompositionTargetRendering
{
get
{
return m_renderOnCompositionTargetRendering;
}
set
{
/* If it is being set to true and it was previously false
* then hook into the event */
if (value && !m_renderOnCompositionTargetRendering)
CompositionTarget.Rendering += CompositionTarget_Rendering;
else if(!value)
CompositionTarget.Rendering -= CompositionTarget_Rendering;
m_renderOnCompositionTargetRendering = value;
m_renderOnCompositionTargetRenderingTemp = value;
}
}
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
InternalInvalidateVideoImage();
}
/// <summary>
/// Used as a clone for a D3DRenderer
/// </summary>
private class ClonedD3DRenderer : D3DRenderer
{}
/// <summary>
/// Creates a clone of the D3DRenderer. This is a work for the visual
/// brush not working cross-threaded
/// </summary>
/// <returns></returns>
public D3DRenderer CloneD3DRenderer()
{
var renderer = new ClonedD3DRenderer();
lock(m_clonedD3Drenderers)
{
m_clonedD3Drenderers.Add(new WeakReference(renderer));
}
renderer.SetBackBuffer(m_pBackBuffer);
return renderer;
}
/// <summary>
/// Cleans up any dead references we may have to any cloned renderers
/// </summary>
private void CleanZombieRenderers()
{
lock(m_clonedD3Drenderers)
{
var deadObjects = new List<WeakReference>();
for (int i = 0; i < m_clonedD3Drenderers.Count; i++)
{
if (!m_clonedD3Drenderers[i].IsAlive)
deadObjects.Add(m_clonedD3Drenderers[i]);
}
foreach(var deadGuy in deadObjects)
{
m_clonedD3Drenderers.Remove(deadGuy);
}
}
}
/// <summary>
/// Configures D3DImage with a new surface. The back buffer is
/// not set until we actually receive a frame, this way we
/// can avoid a black flicker between media changes
/// </summary>
/// <param name="backBuffer">The unmanaged pointer to the Direct3D Surface</param>
protected void SetBackBuffer(IntPtr backBuffer)
{
/* We only do this if target rendering is enabled because we must use an Invoke
* instead of a BeginInvoke to keep the Surfaces in sync and Invoke could be dangerous
* in other situations */
if(RenderOnCompositionTargetRendering)
{
if (!D3DImage.Dispatcher.CheckAccess())
{
D3DImage.Dispatcher.Invoke((Action)(() => SetBackBuffer(backBuffer)), DispatcherPriority.Render);
return;
}
}
/* Flag a new surface */
m_newSurfaceAvailable = true;
m_pBackBuffer = backBuffer;
/* Make a special case for target rendering */
if (RenderOnCompositionTargetRendering)
{
SetBackBufferInternal(m_pBackBuffer);
}
SetBackBufferForClones();
}
/// <summary>
/// Sets the backbuffer for any cloned D3DRenderers
/// </summary>
private void SetBackBufferForClones()
{
lock (m_clonedD3Drenderers)
{
CleanZombieRenderers();
foreach (var rendererRef in m_clonedD3Drenderers)
{
var renderer = rendererRef.Target as D3DRenderer;
if (renderer != null)
renderer.SetBackBuffer(m_pBackBuffer);
}
}
}
/// <summary>
/// Configures D3DImage with a new surface. This happens immediately
/// </summary>
private void SetBackBufferInternal(IntPtr backBuffer)
{
/* Do nothing if we don't have a new surface available */
if (!m_newSurfaceAvailable)
return;
if(!D3DImage.Dispatcher.CheckAccess())
{
D3DImage.Dispatcher.BeginInvoke((Action)(() => SetBackBufferInternal(backBuffer)));
return;
}
/* We have this around a try/catch just in case we
* lose the device and our Surface is invalid. The
* try/catch may not be needed, but testing needs
* to take place before it's removed */
try
{
D3DImage.Lock();
D3DImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, backBuffer);
D3DImage.Unlock();
SetNaturalWidthHeight();
}
catch (Exception)
{ }
/* Clear our flag, so this won't be ran again
* until a new surface is sent */
m_newSurfaceAvailable = false;
}
private void SetNaturalWidthHeight()
{
SetNaturalVideoHeight(m_d3dImage.PixelHeight);
SetNaturalVideoWidth(m_d3dImage.PixelWidth);
}
protected void InvalidateVideoImage()
{
if (!m_renderOnCompositionTargetRendering)
InternalInvalidateVideoImage();
}
/// <summary>
/// Invalidates the entire Direct3D image, notifying WPF to redraw
/// </summary>
protected void InternalInvalidateVideoImage()
{
/* Ensure we run on the correct Dispatcher */
if(!D3DImage.Dispatcher.CheckAccess())
{
D3DImage.Dispatcher.BeginInvoke((Action)(() => InvalidateVideoImage()));
return;
}
/* If there is a new Surface to set,
* this method will do the trick */
SetBackBufferInternal(m_pBackBuffer);
/* Only render the video image if possible, or if IsRenderingEnabled is true */
if (D3DImage.IsFrontBufferAvailable && IsRenderingEnabled && m_pBackBuffer != IntPtr.Zero)
{
try
{
/* Invalidate the entire image */
D3DImage.Lock();
D3DImage.AddDirtyRect(new Int32Rect(0, /* Left */
0, /* Top */
D3DImage.PixelWidth, /* Width */
D3DImage.PixelHeight /* Height */));
D3DImage.Unlock();
}
catch
{ }
}
/* Invalidate all of our cloned D3DRenderers */
InvalidateClonedVideoImages();
}
/// <summary>
/// Invalidates any possible cloned renderer we may have
/// </summary>
private void InvalidateClonedVideoImages()
{
lock(m_clonedD3Drenderers)
{
CleanZombieRenderers();
foreach(var rendererRef in m_clonedD3Drenderers)
{
var renderer = rendererRef.Target as D3DRenderer;
if(renderer != null)
renderer.InvalidateVideoImage();
}
}
}
}
}
In the InitializeD3DVideo() function, I try to read a xml file from a location from the harddisk. But it can't be done.
Is it that the d3drenderer.cs cannot read from a xml file? I just want to read a value from _nABC of the xml file.
This has nothing to do with the D3DRenderer.
The path to the XML file is wrong. There is a '/' (or '\') missing between the workingDirectory and the + "ProfilesDisplay.xml".
If you don't know your path strings, the best method to combine these is System.IO.Path.Combine.
I am using XMGUnityLib Plugin. The program runs fine in the editor without any error but when I try to build it (Android) then while at pushing apk step it gives this error:
InvalidOperationException: The following game object is invoking the DontDestroyOnLoad method: AnalyticsManager. Notice that DontDestroyOnLoad can only be used in play mode and, as such, cannot be part of an editor script.
XMGSingleton`1[XMGAnalyticsManager].get_Instance () (at Assets/XMG/UnityLib/Util/XMGSingleton.cs:28)
AndroidStorePostProcess.OnPostprocessScene () (at Assets/Editor/AndroidStorePostProcess.cs:32)
UnityEditor.HostView:OnGUI()
XMGSingleton.cs:
using UnityEngine;
using System.Collections;
using System;
/// <summary>
/// XMG singleton base Class.
/// Inherit your Manager Monobehavior classes that you want to be Singletons
/// </summary>
public abstract class XMGSingleton<T> : MonoBehaviour where T : MonoBehaviour
{
protected static T instance;
/// <summary>
/// Returns the instance of this singleton
/// Make sure we don't get destroyed on Scene Load
/// </summary>
public static T Instance {
get {
if(instance == null) {
instance = (T) FindObjectOfType(typeof(T));
if (instance == null) {
Debug.LogError("An instance of " + typeof(T) +
" is needed in the scene, but there is none.");
return null;
}
DontDestroyOnLoad(instance.gameObject);
}
return instance;
}
}
#region Public Methods
public static bool HasInstance {
get {
if( (T) FindObjectOfType(typeof(T)) != null ) {
return true;
} else {
return false;
}
}
}
/// <summary>
/// Helper to cast the Instance to the Type provided
/// </summary>
public static Type MyInstance<Type> () {
return (Type)(object)Instance;
}
#endregion
}
XMGAnalyticsManager.cs:
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using Com.XMG.UnityLib.Community.OnlineGame;
using Com.XMG.UnityLib.Tracking;
#if UNITY_IPHONE
using FlurryAccess = FlurryBinding;
#elif UNITY_ANDROID
using FlurryAccess = FlurryAndroid;
#endif
public abstract partial class XMGAnalyticsManager : XMGSingleton<XMGAnalyticsManager> {
#region Private Fields
/// <summary>
/// The type of the current build.
/// For Analytics QA testing point to Analytics
/// For QA Testing point to SandBox
/// For Production/Release build point to Production
/// </summary>
[SerializeField]
private BuildTypeEnum buildType = BuildTypeEnum.SandBox;
/// <summary>
/// The type of the android store type
/// </summary>
[SerializeField]
private AndroidStoreTypeEnum androidStoreType = AndroidStoreTypeEnum.Google;
/// <summary>
/// The swrve authentication Info
/// </summary>
[SerializeField]
private SwrveAuthentication swrveAuth;
/// <summary>
/// The flurry auth.
/// </summary>
[SerializeField]
private FlurryAuthentication flurryAuth;
/// <summary>
/// Sends events every X seconds.
/// </summary>
[SerializeField]
private float sendEventsEverySeconds = 30;
/// <summary>
/// The parent screen.
/// </summary>
[SerializeField]
private string parentScreen = "SplashPage";
/// <summary>
/// The grand parent screen.
/// </summary>
string grandParentScreen = "null";
/// <summary>
/// My external IP.
/// </summary>
string myExtIP = "";
/// <summary>
/// The swrve component.
/// </summary>
private SwrveComponent swrveComponent;
private Dictionary<string, JSONObject> swrveResources = new Dictionary<string, JSONObject>();
/// <summary>
/// Resource hashmap ----> (key: resource name, value: json object)
/// </summary>
public Dictionary<string, JSONObject> Resources {
get {
return this.swrveResources;
}
}
/// <summary>
/// This event is triggered when swrve data is updated.
/// </summary>
public static event Action ItemsConfigurationUpdatedEvent = delegate {};
/// <summary>
/// True if we are in the fetching of AB Test Data State
/// </summary>
private bool isFetchingABParams = false;
/// <summary>
/// The current swrve identifier.
/// </summary>
private string currentSwrveId = null;
/// <summary>
/// The android bundle version.
/// </summary>
[SerializeField]
protected string androidBundleVersion = "1.0";
#endregion
#region Public Properties
/// <summary>
/// True if it's a production build
/// </summary>
public bool IsProductionBuild {
get { return buildType == BuildTypeEnum.Production; }
}
/// <summary>
/// Gets the type of the android store.
/// </summary>
public AndroidStoreTypeEnum AndroidStoreType {
get { return androidStoreType; }
set { androidStoreType = value; }
}
/// <summary>
/// True if its a google play store build
/// </summary>
public bool IsGoogleStoreType {
get { return androidStoreType == AndroidStoreTypeEnum.Google; }
}
/// <summary>
/// True if its an Amazon store build
/// </summary>
public bool IsAmazonStoreType {
get { return androidStoreType == AndroidStoreTypeEnum.Amazon; }
}
#endregion
#region Monobehaviors & Init
/// <summary>
/// Add event Listeners
/// </summary>
void Awake() {
XMGOnlineProfileManager.OnProfileLoadedEvent += ProfileHasLoadedCallback;
XMGCommunityManager.OnCreateRealProfileOnServerEvent += ProfileCreatedOnServerCallback;
XMGCommunityManager.OnNewFriendsAddedEvent += NewFriendsAddedCallback;
RegisterGameListeners();
// TODO: Listen for Profile Change events
swrveAuth.Init();
flurryAuth.Init();
}
/// <summary>
/// Starts the SWRVE Event timer
/// </summary>
void Start() {
StartSwrveTimer();
StartCoroutine( CheckIP() );
}
/// <summary>
/// Checks for the IP
/// </summary>
/// <returns>
/// The IP
/// </returns>
IEnumerator CheckIP(){
WWW myExtIPWWW = new WWW("http://checkip.dyndns.org");
if( myExtIPWWW != null ) {
yield return myExtIPWWW;
if( myExtIPWWW.error == null ) {
myExtIP=myExtIPWWW.text;
myExtIP=myExtIP.Substring(myExtIP.IndexOf(":")+1);
myExtIP=myExtIP.Substring(0,myExtIP.IndexOf("<"));
}
}
}
/// <summary>
/// Detach event Listeners
/// </summary>
void OnDestroy() {
XMGOnlineProfileManager.OnProfileLoadedEvent -= ProfileHasLoadedCallback;
XMGCommunityManager.OnCreateRealProfileOnServerEvent -= ProfileCreatedOnServerCallback;
XMGCommunityManager.OnNewFriendsAddedEvent -= NewFriendsAddedCallback;
UnRegisterGameListeners();
}
/// <summary>
/// If Pause end session
/// If Resume Start session and Re-Login.
/// At any case post events
/// </summary>
/// <param name='pause'>
/// Pause.
/// </param>
void OnApplicationPause(bool pause) {
if (currentSwrveId != null) {
if(pause) {
swrveComponent.Swrve_AddSessionEnd();
} else {
swrveComponent.Swrve_AddSessionStart();
SessionStart();
AnalyticsLogout();
AnalyticsLogin();
}
LogUserProperties();
swrveComponent.Swrve_PostEvents(currentSwrveId, SWRVE_APP_VERSION);
}
}
/// <summary>
/// Init plugins.The Profile Has loaded
/// </summary>
void ProfileHasLoadedCallback() {
// Get Swrve component
swrveComponent = gameObject.GetComponentInChildren<SwrveComponent>();
AnalyticsLogin();
// Check if we've set the random user property, if not then set it
if (!PlayerPrefs.HasKey(RANDOM_TEST_PROPERTY_KEY)) {
PlayerPrefs.SetString(RANDOM_TEST_PROPERTY_KEY, "true");
JSONObject userProperty = new JSONObject(JSONObject.Type.OBJECT);
int random = NGUITools.RandomRange(1, 100);
userProperty.AddField("RandNum", random.ToString());
swrveComponent.Swrve_AddUserUpdateEvent(userProperty.ToString());
}
}
/// <summary>
/// A new Online Profile created callback.
/// </summary>
void ProfileCreatedOnServerCallback() {
AddNamedEvent( "Community.FacebookUserCreated" );
}
/// <summary>
/// New friends were added callback.
/// </summary>
/// <param name='newFriendsAdded'>
/// The New friends added
/// </param>
void NewFriendsAddedCallback( int newFriendsAdded ) {
JSONObject jTournamentFriends = new JSONObject(JSONObject.Type.OBJECT);
jTournamentFriends.AddField( "NewFriendsAdded", BucketGeneral( newFriendsAdded ) );
AddNamedEvent ( "Community.TournamentAddedFriend", jTournamentFriends );
}
#endregion
#region Abstract & Virtual Methods
/// <summary>
/// Gets the tracking Object
/// </summary>
protected abstract XMGTracking GetTracking();
/// <summary>
/// Registers the game listeners.
/// </summary>
protected abstract void RegisterGameListeners();
/// <summary>
/// Unregister the game listeners.
/// </summary>
protected abstract void UnRegisterGameListeners();
/// <summary>
/// Logs the game user properties.
/// </summary>
/// <param name='jUserProperties'>
/// The JSON Object with the user properties.
/// </param>
protected abstract void LogGameUserProperties(JSONObject jUserProperties);
/// <summary>
/// A session Is Started
/// </summary>
protected abstract void SessionStart();
/// <summary>
/// The session has ended
/// </summary>
protected abstract void SessionEnd();
/// <summary>
/// Gets the user start time.
/// Default to Now
/// Override to provide the user's start time
/// </summary>
/// <returns>
/// The user start time.
/// </returns>
protected virtual long GetUserStartTime() {
return (long)XMGUnityUtil.ToUnixTimeFromUniversal( DateTime.UtcNow );
}
#endregion
#region Private Methods
/// <summary>
/// Starts the swrve timer.
/// </summary>
private void StartSwrveTimer() {
StartCoroutine( StartEventsTimer());
}
/// <summary>
/// Logs the user properties.
/// And Calls abstract LogGameUserProperties
/// </summary>
private void LogUserProperties() {
JSONObject jUserProperties = new JSONObject(JSONObject.Type.OBJECT);
// Add here Common User Properties
XMGTracking tracking = GetTracking();
jUserProperties.AddField( UP_FIRST_LAUNCH_DAY, tracking.FirstLaunch.Day );
jUserProperties.AddField( UP_FIRST_LAUNCH_MONTH, tracking.FirstLaunch.Month );
jUserProperties.AddField( UP_FIRST_LAUNCH_YEAR, tracking.FirstLaunch.Year );
jUserProperties.AddField( UP_FIRST_LAUNCH_EPOCH, XMGUnityUtil.ToUnixTimeFromUniversal( tracking.FirstLaunch ).ToString() );
jUserProperties.AddField( UP_TOTAL_TAPJOY_REWARDS, tracking.TotalTapJoyPointsEarned );
jUserProperties.AddField( UP_TOTAL_SPONSORPAY_REWARDS, tracking.TotalSponsorPayPointsEarned );
jUserProperties.AddField( UP_TOTAL_CHARTBOOST_ADS_CLICKED, tracking.TotalChartBoostAdsClicked );
jUserProperties.AddField( UP_TOTAL_CHARTBOOST_ADS_CLOSED, tracking.TotalChartBoostAdsClosed );
jUserProperties.AddField( UP_TOTAL_SOFT_CURRENCY_EARNED, tracking.SoftCurrencyEarned );
jUserProperties.AddField( UP_TOTAL_HARD_CURRENCY_EARNED, tracking.HardCurrencyEarned );
jUserProperties.AddField( UP_TOTAL_SOFT_CURRENCY_SPENT, tracking.SoftCurrencySpent );
jUserProperties.AddField( UP_TOTAL_HARD_CURRENCY_SPENT, tracking.HardCurrencySpent );
#if UNITY_IPHONE
jUserProperties.AddField( UP_VERION, EtceteraTwoBinding.getInfoPlistValue("CFBundleVersion") );
#elif UNITY_ANDROID
jUserProperties.AddField( UP_VERION, androidBundleVersion );
#endif
jUserProperties.AddField( UP_TOTAL_FRIENDS, tracking.TotalFriends );
jUserProperties.AddField( UP_TOTAL_UNIQUE_DAYS_PLAYED, tracking.TotalUniqueDaysPlayed );
if( string.IsNullOrEmpty( myExtIP ) ) {
jUserProperties.AddField( UP_IP, myExtIP );
}
LogGameUserProperties(jUserProperties);
swrveComponent.Swrve_AddUserUpdateEvent(jUserProperties.ToString());
}
#endregion
#region Authentication
/// <summary>
/// Logs in to Flurry and SWRVE
/// </summary>
void AnalyticsLogin() {
FlurryLogin();
if( currentSwrveId != null ) {
if( !currentSwrveId.Equals( XMGOnlineProfileManager.Instance.ActiveOnlineProfile.GameStats.SwrveId ) ) {
Debug.Log("There is already a swrve account signed in!!! You must close the session first!");
AnalyticsLogout();
} else {
// This swrve user is already logged in, bail out of this function
Debug.Log("Attempt to log into swrve twice with the same id... Ignoring.");
return;
}
}
SwrveLogin(XMGOnlineProfileManager.Instance.ActiveOnlineProfile.GameStats.SwrveId);
}
/// <summary>
/// Logout from Flurry and SWRVE
/// </summary>
private void AnalyticsLogout() {
FlurryLogout();
if(currentSwrveId == null) {
Debug.LogError("There is no active swrve user!!! Cannot close session.");
return;
}
SwrveLogout();
}
/// <summary>
/// Starts Flurry Session
/// </summary>
private void FlurryLogin() {
#if UNITY_IPHONE
FlurryAccess.startSession(flurryAuth.FlurryKey);
#elif UNITY_ANDROID
FlurryAccess.onStartSession(flurryAuth.FlurryKey, true, true );
#endif
}
/// <summary>
/// Logouts from Flurry
/// </summary>
private void FlurryLogout() {
#if UNITY_ANDROID
FlurryAccess.onEndSession();
#endif
}
/// <summary>
/// Login to Swrve
/// </summary>
/// <param name='swrveId'>
/// The Swrve identifier.
/// </param>
private void SwrveLogin(string swrveId) {
currentSwrveId = swrveId;
int gameId = 0;
string apiKey = "";
if (buildType == BuildTypeEnum.Analytics) {
gameId = swrveAuth.AnalyticsGameID;
apiKey = swrveAuth.AnalyticsAPIKey;
} else if (buildType == BuildTypeEnum.SandBox) {
gameId = swrveAuth.SandboxGameID;
apiKey = swrveAuth.SandboxAPIKey;
} else if( buildType == BuildTypeEnum.Production ) {
gameId = swrveAuth.ProductionGameID;
apiKey = swrveAuth.ProductionAPIKey;
}
InitializeSWRVEComponent(gameId,
apiKey,
swrveAuth.PersonalKey,
swrveAuth.URLABTest
);
}
/// <summary>
/// Logout from Swrve
/// </summary>
private void SwrveLogout() {
// Make sure to close the Swrve session
SessionEnd();
swrveComponent.Swrve_AddSessionEnd();
swrveComponent.Swrve_PostEvents(currentSwrveId, SWRVE_APP_VERSION);
currentSwrveId = null;
}
/// <summary>
/// Initializes the SWRVE component.
/// </summary>
/// <param name='gameId'>
/// The Game identifier.
/// </param>
/// <param name='apiKey'>
/// The API key.
/// </param>
/// <param name='personalKey'>
/// Personal key.
/// </param>
/// <param name='abURL'>
/// The Ab Test server URL.
/// </param>
private void InitializeSWRVEComponent(int gameId, string apiKey, string personalKey, string abURL) {
swrveComponent.gameId = gameId;
swrveComponent.apiKey = apiKey;
swrveComponent.abTestServer = abURL;
// Setup swrve for new user
swrveComponent.Swrve_AddSessionStart();
SessionStart();
if(isFetchingABParams == false) {
isFetchingABParams = true;
StartCoroutine(GetAllResourcesWithABTests());
}
}
#endregion
#region A/B Helpers
/// <summary>
/// Returns the most recent JSON configuration for a resource from swrve or NULL if there is none.
/// This means it could return cached data that is old. If you want to retrieve the latest from swrve.
/// </summary>
/// <param name="itemId">
/// A <see cref="System.String"/>
/// </param>
/// <returns>
/// A <see cref="JSONObject"/>
/// </returns>
public JSONObject GetItemParams(string itemId) {
JSONObject item = null;
swrveResources.TryGetValue(itemId, out item);
return item;
}
/// <summary>
/// Gets all resources with AB tests.
/// </summary>
/// <returns>
/// The all resources with AB tests.
/// </returns>
private IEnumerator GetAllResourcesWithABTests() {
string swrveURLRequest = string.Format(swrveAuth.URLABTestResources + "?api_key={0}&user={1}&joined={2}", swrveComponent.apiKey, currentSwrveId, GetUserStartTime() );
WWW itemRequest = new WWW(swrveURLRequest);
yield return itemRequest; // Yield until a result is returned.
if(itemRequest.error != null) {
Debug.LogWarning("Error attempting to fetch Swrve A/B Resource data: " + itemRequest.error);
LoadCachedConfig();
// Bail!
yield break;
}
// Process all the resources into a hashmap ( key: resource name, value: json object )
JSONObject jResources = new JSONObject(itemRequest.text);
if(jResources == null || jResources.type == JSONObject.Type.NULL) {
// Bad data from swrve, abort!
Debug.LogError("Bad A/B resource data from swrve!");
LoadCachedConfig();
yield break;
} else {
//Debug.LogWarning( "Data from SWRVE: " + jResources );
}
XMGSaveLoadUtils.Instance.SaveEncryptedField(CACHED_CONFIG_KEY, itemRequest.text); // Replace the old cached config.
ParseResources(jResources);
if( swrveResources.Count > 0 ) {
ItemsConfigurationUpdatedEvent();
}
isFetchingABParams = false;
}
/// <summary>
/// Loads the cached swrve config file
/// </summary>
private void LoadCachedConfig() {
string cached = XMGSaveLoadUtils.Instance.LoadEncryptedField(CACHED_CONFIG_KEY);
if(string.IsNullOrEmpty(cached)) {
// There was no cached data
return;
}
// Apply the cached resources so it doesn't block updates.
JSONObject jResources = new JSONObject(cached);
ParseResources(jResources);
if( swrveResources.Count > 0 ) {
ItemsConfigurationUpdatedEvent();
}
}
/// <summary>
/// Expects a JSON array of resources.
/// </summary>
/// <param name="resources">
/// A <see cref="JSONObject"/>
/// </param>
private void ParseResources(JSONObject jResources) {
swrveResources.Clear();
//Debug.Log( "Parsing SWRVE Resources: " + jResources );
if(jResources == null || jResources.type != JSONObject.Type.ARRAY) {
// Bad data from swrve, abort!
Debug.LogError("Could not parse resource data, unexpected format!");
return;
}
foreach(JSONObject data in jResources.list) {
JSONObject key = data.GetField("uid");
if(key == null || key.type != JSONObject.Type.STRING || string.IsNullOrEmpty( JSONObject.GetString(key) ) ) {
// Bad item, on to the next
Debug.LogWarning("Bad item, no property 'uid' in " + data.print());
continue;
}
// Add the resource over top of any precached configuration
swrveResources[JSONObject.GetString(key)] = data;
}
}
#endregion
#region Event Handling
/// <summary>
/// Adds the named event.
/// </summary>
/// <param name='name'>
/// The EventName.
/// </param>
public void AddNamedEvent(string name) {
swrveComponent.Swrve_AddNamedEvent(name, "{}");
#if UNITY_IPHONE
FlurryAccess.logEvent(name, false);
#elif UNITY_ANDROID
FlurryAccess.logEvent(name);
#endif
}
/// <summary>
/// Adds a named event with Payload
/// </summary>
/// <param name='name'>
/// The EventName.
/// </param>
/// <param name='payload'>
/// Payload.
/// </param>
public void AddNamedEvent(string name, JSONObject payload) {
string data = payload.ToString();
if(!string.IsNullOrEmpty(data)) {
swrveComponent.Swrve_AddNamedEvent(name, data);
} else {
swrveComponent.Swrve_AddNamedEvent(name, "{}");
}
#if UNITY_IPHONE
FlurryAccess.logEventWithParameters(name, payload.ToDictionary(), false);
#elif UNITY_ANDROID
FlurryAccess.logEvent(name, payload.ToDictionary());
#endif
}
/// <summary>
/// Infinite Loop to send events in the queue
/// </summary>
private IEnumerator StartEventsTimer() {
float timeInterval = Time.realtimeSinceStartup;
while (true) {
if(Time.realtimeSinceStartup - timeInterval >= sendEventsEverySeconds) {
SendEvents();
timeInterval = Time.realtimeSinceStartup;
}
yield return new WaitForSeconds( 1 );
}
}
/// <summary>
/// Sends the events to SWRVE
/// </summary>
private void SendEvents() {
if(currentSwrveId != null) {
swrveComponent.Swrve_PostEvents(currentSwrveId, SWRVE_APP_VERSION);
}
}
#endregion
#region UI Events
/// <summary>
/// Logs a UI screen visit event.
/// Records the current screen, the parent screen, and the grandparent screen.
/// </summary>
/// <param name='screenName'>
/// Screen name.
/// </param>
public void ScreenVisitEvent(string screenName) {
JSONObject eventParams = new JSONObject(JSONObject.Type.OBJECT);
if(parentScreen != null) {
eventParams.AddField("parent", parentScreen);
}
if(grandParentScreen != null) {
eventParams.AddField("grandParent", grandParentScreen);
}
AddNamedEvent("UI.Flow." + screenName, eventParams);
eventParams.AddField("screen", screenName);
AddNamedEvent("UI.Flow.Screens", eventParams);
grandParentScreen = parentScreen;
parentScreen = screenName;
}
/// <summary>
/// Logs a UI button pressed event
/// </summary>
/// <param name='buttonName'>
/// The Button name.
/// </param>
public void ButtonPressedEvent( string buttonName ) {
AddNamedEvent("UI.ButtonPressed." + buttonName );
}
#endregion
#region Purchase Events
/// <summary>
/// Sends an analytics buy in event for purchasing inapps.
/// </summary>
/// <param name='rewardCurrency'>
/// Reward currency
/// </param>
/// <param name='rewardAmount'>
/// Amount of rewardCurrency
/// </param>
/// <param name='localCost'>
/// The real money price in local currency ( e.g 0.99 )
/// </param>
/// <param name='localCurrency'>
/// CAD, USD, etc...
/// </param>
public void AddBuyInEvent(string rewardCurrency, int rewardAmount, float localCost, string localCurrency, string itemID ) {
swrveComponent.Swrve_AddBuyInEvent( "", rewardCurrency, rewardAmount, Mathf.Round(localCost* 100)/100, localCurrency );
AddNamedEvent( "Purhases.IAP" );
AddNamedEvent( "Purhases.IAP.HardCurrency" + itemID );
}
/// <summary>
/// Analytics event when a user purchases an item in-game.
/// </summary>
/// <param name='itemID'>
/// The UID for the Item
/// </param>
/// <param name='currency'>
/// The currency
/// </param>
/// <param name='itemCost'>
/// Item cost.
/// </param>
/// <param name='itemQuantity'>
/// Item quantity.
/// </param>
public void AddPurchaseEvent( string itemID, string currency, int itemCost, int itemQuantity ) {
swrveComponent.Swrve_AddPurchaseItemEvent( itemID, currency, itemCost, itemQuantity );
}
/// <summary>
/// Adds a purchase conversion event.
/// </summary>
/// <param name='itemID'>
///The unique Item ID
/// </param>
public void AddPurchaseConversionPackEvent( string itemID ) {
AddNamedEvent( "Purchases.HardCurrency.SoftCurrency" );
AddNamedEvent( "Purhases.HardCurrency.SoftCurrency." + itemID );
}
#endregion
}
Is this singleton class correctly coded? If yes, what could be the problem?
Any help will be appreciated.
Thanks :)
Do you have an "Editor Script" that inherits from this class? If so then most probably this is what's causing the error. If you're not sure what this means, try looking for a folder in your project named Editor and looking in its scripts. Or try searching for any scripts that are using UnityEditor; and that inherit from Editor like this: public class Name : Editor and make them not inherit from this class.
Another solution is try commenting the DontDestroyOnLoad line (line 26 in XMGSingleton.cs) and put it yourself where you need it.
Go to the objects that you're using this script with and do something like this:
void Awake()
{
if (instance == null)
{
DontDestroyOnLoad(gameObject);
instance = this;
}
else
if (instance != this)
Destroy(gameObject);
}
Though the post is a bit old, I recently bumped into this issue. It was a bit confusing because I had not added any new editor script and other editor scripts were not being called from the Singleton GameObject that the error was pointing to.
When I tried restarting Unity and then again created the build, it started giving some other exception.
However, I finally found the issue was being caused by a method from another object (created by the Singleton's OnAwake method) trying to access an uninitialized property of the Singleton (because the OnAwake method execution was not completed).
I fixed it by delaying the other object's method callback based on a different event.
I have read many posts on binding an ObservableCollection to a ListView from people with similar issues; however, I haven't found a solution for my usecase, yet.
In the following test application, I have a simple ListView and a Button. On startup, the ListView is initialized, i.e. 2 columns and 30 rows with values from 0-29 are created. Half of the 30 rows (i.e. 15 rows) are visible. To see the remaining 15 Items I have to scroll down using the Scrollbar.
The Button is binded to an Asynchronous Command using the AsynchronousCommand Class from this article. When the button is clicked (see Start_Click), random numbers are written into those 30 rows of the ListView. This is done in an endless loop of a separate Thread (see AsynchronousCommand).
Now, when I click on the button, I would expect all ListView Items to change to random values instantaneously. However, this is not what's happening. Instead, only those Items that are not visible (i.e. the 15 Items beyond the ScrollBar) are changing their values. Sometimes, none of the Items changes its value.
Here's the XAML:
<Window x:Class="ListViewTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="614">
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="38*"/>
<ColumnDefinition Width="9*"/>
</Grid.ColumnDefinitions>
<ListView HorizontalAlignment="Left" ItemsSource="{Binding MyList}" Height="261" Margin="28,24,0,0" VerticalAlignment="Top" Width="454" Grid.ColumnSpan="2">
<ListView.View>
<GridView>
<GridViewColumn Width="100" Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Width="325" Header="Data" DisplayMemberBinding="{Binding Data}" />
</GridView>
</ListView.View>
</ListView>
<Button Content="Start" Command="{Binding StartCommand}" Grid.Column="1" HorizontalAlignment="Left" Margin="21.043,42,0,0" VerticalAlignment="Top" Width="75"/>
</Grid>
</Window>
This is my Code (View's CodeBehind, ViewModel, Controller Logic, and Model):
/// <summary>
/// This is the CodeBehind of my View
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
LayoutRoot.DataContext = new ViewModel();
}
}
/// <summary>
/// This is my ViewModel
/// </summary>
public class ViewModel : NotifyPropertyChanged
{
private ObservableCollection<Document> _myList;
private Logic _logic;
private AsynchronousCommand _startCommand;
public ViewModel()
{
_myList = new ObservableCollection<Document>();
_logic = new Logic(this);
_startCommand = new AsynchronousCommand(_logic.Start_Click, true);
}
public ObservableCollection<Document> MyList
{
get { return _myList; }
set
{
if (_myList != value)
{
_myList = value;
RaisePropertyChangedEvent("MyList");
}
}
}
public AsynchronousCommand StartCommand
{
get
{
return _startCommand;
}
set
{
_startCommand = value;
}
}
}
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChangedEvent(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// This is my Controller
/// </summary>
public class Logic
{
private ViewModel _viewModel;
private Random _rnd;
public Logic(ViewModel vm)
{
_viewModel = vm;
_rnd = new Random();
for (int i = 0; i < 30; i++)
{
Document newDocument = new Document("Name " + i.ToString(), "Data " + i.ToString());
_viewModel.MyList.Add(newDocument);
}
}
public void Start_Click(object obj)
{
while (true)
{
int idx = _rnd.Next(0, 29);
_viewModel.StartCommand.ReportProgress(() =>
{
_viewModel.MyList[idx].Name = "New Name";
_viewModel.MyList[idx].Data = "New Data";
});
System.Threading.Thread.Sleep(20);
}
}
}
/// <summary>
/// This is my Model
/// </summary>
public class Document
{
public string Name { get; set; }
public string Data { get; set; }
public Document(string name, string data)
{
Name = name;
Data = data;
}
}
And this is the Code for my AsynchronousCommand, taken from Dave Kerr's article on CodeProject:
/// <summary>
/// The ViewModelCommand class - an ICommand that can fire a function.
/// </summary>
public class Command : ICommand
{
/// <summary>
/// Initializes a new instance of the <see cref="Command"/> class.
/// </summary>
/// <param name="action">The action.</param>
/// <param name="canExecute">if set to <c>true</c> [can execute].</param>
public Command(Action action, bool canExecute = true)
{
// Set the action.
this.action = action;
this.canExecute = canExecute;
}
/// <summary>
/// Initializes a new instance of the <see cref="Command"/> class.
/// </summary>
/// <param name="parameterizedAction">The parameterized action.</param>
/// <param name="canExecute">if set to <c>true</c> [can execute].</param>
public Command(Action<object> parameterizedAction, bool canExecute = true)
{
// Set the action.
this.parameterizedAction = parameterizedAction;
this.canExecute = canExecute;
}
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="param">The param.</param>
public virtual void DoExecute(object param)
{
// Invoke the executing command, allowing the command to be cancelled.
CancelCommandEventArgs args = new CancelCommandEventArgs() { Parameter = param, Cancel = false };
InvokeExecuting(args);
// If the event has been cancelled, bail now.
if (args.Cancel)
return;
// Call the action or the parameterized action, whichever has been set.
InvokeAction(param);
// Call the executed function.
InvokeExecuted(new CommandEventArgs() { Parameter = param });
}
protected void InvokeAction(object param)
{
Action theAction = action;
Action<object> theParameterizedAction = parameterizedAction;
if (theAction != null)
theAction();
else if (theParameterizedAction != null)
theParameterizedAction(param);
}
protected void InvokeExecuted(CommandEventArgs args)
{
CommandEventHandler executed = Executed;
// Call the executed event.
if (executed != null)
executed(this, args);
}
protected void InvokeExecuting(CancelCommandEventArgs args)
{
CancelCommandEventHandler executing = Executing;
// Call the executed event.
if (executing != null)
executing(this, args);
}
/// <summary>
/// The action (or parameterized action) that will be called when the command is invoked.
/// </summary>
protected Action action = null;
protected Action<object> parameterizedAction = null;
/// <summary>
/// Bool indicating whether the command can execute.
/// </summary>
private bool canExecute = false;
/// <summary>
/// Gets or sets a value indicating whether this instance can execute.
/// </summary>
/// <value>
/// <c>true</c> if this instance can execute; otherwise, <c>false</c>.
/// </value>
public bool CanExecute
{
get { return canExecute; }
set
{
if (canExecute != value)
{
canExecute = value;
EventHandler canExecuteChanged = CanExecuteChanged;
if (canExecuteChanged != null)
canExecuteChanged(this, EventArgs.Empty);
}
}
}
#region ICommand Members
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
/// <returns>
/// true if this command can be executed; otherwise, false.
/// </returns>
///
bool ICommand.CanExecute(object parameter)
{
return canExecute;
}
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
void ICommand.Execute(object parameter)
{
this.DoExecute(parameter);
}
#endregion
/// <summary>
/// Occurs when can execute is changed.
/// </summary>
public event EventHandler CanExecuteChanged;
/// <summary>
/// Occurs when the command is about to execute.
/// </summary>
public event CancelCommandEventHandler Executing;
/// <summary>
/// Occurs when the command executed.
/// </summary>
public event CommandEventHandler Executed;
}
/// <summary>
/// The CommandEventHandler delegate.
/// </summary>
public delegate void CommandEventHandler(object sender, CommandEventArgs args);
/// <summary>
/// The CancelCommandEvent delegate.
/// </summary>
public delegate void CancelCommandEventHandler(object sender, CancelCommandEventArgs args);
/// <summary>
/// CommandEventArgs - simply holds the command parameter.
/// </summary>
public class CommandEventArgs : EventArgs
{
/// <summary>
/// Gets or sets the parameter.
/// </summary>
/// <value>The parameter.</value>
public object Parameter { get; set; }
}
/// <summary>
/// CancelCommandEventArgs - just like above but allows the event to
/// be cancelled.
/// </summary>
public class CancelCommandEventArgs : CommandEventArgs
{
/// <summary>
/// Gets or sets a value indicating whether this <see cref="CancelCommandEventArgs"/> command should be cancelled.
/// </summary>
/// <value><c>true</c> if cancel; otherwise, <c>false</c>.</value>
public bool Cancel { get; set; }
}
/// <summary>
/// The AsynchronousCommand is a Command that runs on a thread from the thread pool.
/// </summary>
public class AsynchronousCommand : Command, INotifyPropertyChanged
{
/// <summary>
/// Initializes a new instance of the <see cref="AsynchronousCommand"/> class.
/// </summary>
/// <param name="action">The action.</param>
/// <param name="canExecute">if set to <c>true</c> the command can execute.</param>
public AsynchronousCommand(Action action, bool canExecute = true)
: base(action, canExecute)
{
// Initialise the command.
Initialise();
}
/// <summary>
/// Initializes a new instance of the <see cref="AsynchronousCommand"/> class.
/// </summary>
/// <param name="parameterizedAction">The parameterized action.</param>
/// <param name="canExecute">if set to <c>true</c> [can execute].</param>
public AsynchronousCommand(Action<object> parameterizedAction, bool canExecute = true)
: base(parameterizedAction, canExecute)
{
// Initialise the command.
Initialise();
}
/// <summary>
/// Initialises this instance.
/// </summary>
private void Initialise()
{
// Construct the cancel command.
cancelCommand = new Command(
() =>
{
// Set the Is Cancellation Requested flag.
IsCancellationRequested = true;
}, true);
}
/// <summary>
/// Executes the command.
/// </summary>
/// <param name="param">The param.</param>
public override void DoExecute(object param)
{
// If we are already executing, do not continue.
if (IsExecuting)
return;
// Invoke the executing command, allowing the command to be cancelled.
CancelCommandEventArgs args = new CancelCommandEventArgs() { Parameter = param, Cancel = false };
InvokeExecuting(args);
// If the event has been cancelled, bail now.
if (args.Cancel)
return;
// We are executing.
IsExecuting = true;
// Store the calling dispatcher.
callingDispatcher = Dispatcher.CurrentDispatcher;
// Run the action on a new thread from the thread pool (this will therefore work in SL and WP7 as well).
ThreadPool.QueueUserWorkItem(
(state) =>
{
// Invoke the action.
InvokeAction(param);
// Fire the executed event and set the executing state.
ReportProgress(
() =>
{
// We are no longer executing.
IsExecuting = false;
// If we were cancelled, invoke the cancelled event - otherwise invoke executed.
if (IsCancellationRequested)
InvokeCancelled(new CommandEventArgs() { Parameter = param });
else
InvokeExecuted(new CommandEventArgs() { Parameter = param });
// We are no longer requesting cancellation.
IsCancellationRequested = false;
}
);
}
);
}
/// <summary>
/// Raises the property changed event.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
private void NotifyPropertyChanged(string propertyName)
{
// Store the event handler - in case it changes between
// the line to check it and the line to fire it.
PropertyChangedEventHandler propertyChanged = PropertyChanged;
// If the event has been subscribed to, fire it.
if (propertyChanged != null)
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
/// <summary>
/// Reports progress on the thread which invoked the command.
/// </summary>
/// <param name="action">The action.</param>
public void ReportProgress(Action action)
{
if (IsExecuting)
{
if (callingDispatcher.CheckAccess())
action();
else
callingDispatcher.BeginInvoke(((Action)(() => { action(); })));
}
}
/// <summary>
/// Cancels the command if requested.
/// </summary>
/// <returns>True if the command has been cancelled and we must return.</returns>
public bool CancelIfRequested()
{
// If we haven't requested cancellation, there's nothing to do.
if (IsCancellationRequested == false)
return false;
// We're done.
return true;
}
/// <summary>
/// Invokes the cancelled event.
/// </summary>
/// <param name="args">The <see cref="Apex.MVVM.CommandEventArgs"/> instance containing the event data.</param>
protected void InvokeCancelled(CommandEventArgs args)
{
CommandEventHandler cancelled = Cancelled;
// Call the cancelled event.
if (cancelled != null)
cancelled(this, args);
}
protected Dispatcher callingDispatcher;
/// <summary>
/// Flag indicating that the command is executing.
/// </summary>
private bool isExecuting = false;
/// <summary>
/// Flag indicated that cancellation has been requested.
/// </summary>
private bool isCancellationRequested;
/// <summary>
/// The cancel command.
/// </summary>
private Command cancelCommand;
/// <summary>
/// The property changed event.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Occurs when the command is cancelled.
/// </summary>
public event CommandEventHandler Cancelled;
/// <summary>
/// Gets or sets a value indicating whether this instance is executing.
/// </summary>
/// <value>
/// <c>true</c> if this instance is executing; otherwise, <c>false</c>.
/// </value>
public bool IsExecuting
{
get
{
return isExecuting;
}
set
{
if (isExecuting != value)
{
isExecuting = value;
NotifyPropertyChanged("IsExecuting");
}
}
}
/// <summary>
/// Gets or sets a value indicating whether this instance is cancellation requested.
/// </summary>
/// <value>
/// <c>true</c> if this instance is cancellation requested; otherwise, <c>false</c>.
/// </value>
public bool IsCancellationRequested
{
get
{
return isCancellationRequested;
}
set
{
if (isCancellationRequested != value)
{
isCancellationRequested = value;
NotifyPropertyChanged("IsCancellationRequested");
}
}
}
/// <summary>
/// Gets the cancel command.
/// </summary>
public Command CancelCommand
{
get { return cancelCommand; }
}
}
Your Document Class has to implement INotifyPropertyChanged Interface so the UI gets updated once your Properties change.
Why is it working when you scroll?
Because of Virtualization. Not visible entries have not been evualuated yet, so once they get evaluated they already receive the "New Value".
Here is your Document class that works:
/// <summary>
/// This is my Model
/// </summary>
public class Document : INotifyPropertyChanged
{
private string _name;
private string _data;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public string Data
{
get { return _data; }
set
{
_data = value;
OnPropertyChanged();
}
}
public Document(string name, string data)
{
Name = name;
Data = data;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Does someone know a way to keep the visual selection state of a selected text in a RichEditBox? I want to add some basic text editing to my Windows 8.1 App but every time I select a text and click on another UI Element within the app, the RichEditBox hides the selection.
I already tried to register the unfocus event an set the selection range again but unfortunately this has no effect.
I also tried to draw my own rect over the Text using
richEdit.Document.Selection.GetRect(PointOptions.ClientCoordinates,out selectionRect, out hitCount );
This works as long as only some text within a single line is selected. If the selection is multilined I only get the top-left of and the bottom-right position of the selected Text. It seems like these are the mouse Positions where the selection where started and where it ended.
Are there any other ways keep to the selected text visibible when the RichEditBox is unfocused.
I found another workaround. Just set the selection Background when the RichEditBox is unfocused. But Jerry's Post gave me the Inspiration to this solution. Seems like this way was to simple to find it a first place:
private void RichEditOnGotFocus(object sender, RoutedEventArgs routedEventArgs)
{
ITextSelection selectedText = richEdit.Document.Selection;
if (selectedText != null)
{
richEdit.Document.Selection.SetRange(_selectionStart, _selectionEnd);
selectedText.CharacterFormat.BackgroundColor = Colors.White;
}
}
private void RichEditOnLostFocus(object sender, RoutedEventArgs routedEventArgs)
{
_selectionEnd = richEdit.Document.Selection.EndPosition;
_selectionStart = richEdit.Document.Selection.StartPosition;
ITextSelection selectedText = richEdit.Document.Selection;
if (selectedText != null)
{
selectedText.CharacterFormat.BackgroundColor = Colors.Gray;
}
}
In the WinRT Toolkit, there's a HighlightBehavior that doesn't directly solve your problem, but might offer you a solution that is acceptable.
http://winrtxamltoolkit.codeplex.com/SourceControl/latest#WinRTXamlToolkit/WinRTXamlToolkit.Shared/Controls/Behaviors/HighlightBehavior.cs
public class HighlightBehavior : Behavior<TextBlock>
{
#region SearchString
/// <summary>
/// SearchString Dependency Property
/// </summary>
public static readonly DependencyProperty SearchStringProperty =
DependencyProperty.Register(
"SearchString",
typeof(string),
typeof(HighlightBehavior),
new PropertyMetadata(null, OnSearchStringChanged));
/// <summary>
/// Gets or sets the SearchString property. This dependency property
/// indicates the search string to highlight in the associated TextBlock.
/// </summary>
public string SearchString
{
get { return (string)GetValue(SearchStringProperty); }
set { SetValue(SearchStringProperty, value); }
}
/// <summary>
/// Handles changes to the SearchString property.
/// </summary>
/// <param name="d">
/// The <see cref="DependencyObject"/> on which
/// the property has changed value.
/// </param>
/// <param name="e">
/// Event data that is issued by any event that
/// tracks changes to the effective value of this property.
/// </param>
private static void OnSearchStringChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (HighlightBehavior)d;
string oldSearchString = (string)e.OldValue;
string newSearchString = target.SearchString;
target.OnSearchStringChanged(oldSearchString, newSearchString);
}
/// <summary>
/// Provides derived classes an opportunity to handle changes
/// to the SearchString property.
/// </summary>
/// <param name="oldSearchString">The old SearchString value</param>
/// <param name="newSearchString">The new SearchString value</param>
private void OnSearchStringChanged(
string oldSearchString, string newSearchString)
{
UpdateHighlight();
}
#endregion
#region IsCaseSensitive
/// <summary>
/// IsCaseSensitive Dependency Property
/// </summary>
public static readonly DependencyProperty IsCaseSensitiveProperty =
DependencyProperty.Register(
"IsCaseSensitive",
typeof(bool),
typeof(HighlightBehavior),
new PropertyMetadata(false, OnIsCaseSensitiveChanged));
/// <summary>
/// Gets or sets the IsCaseSensitive property. This dependency property
/// indicates whether the highlight behavior is case sensitive.
/// </summary>
public bool IsCaseSensitive
{
get { return (bool)GetValue(IsCaseSensitiveProperty); }
set { SetValue(IsCaseSensitiveProperty, value); }
}
/// <summary>
/// Handles changes to the IsCaseSensitive property.
/// </summary>
/// <param name="d">
/// The <see cref="DependencyObject"/> on which
/// the property has changed value.
/// </param>
/// <param name="e">
/// Event data that is issued by any event that
/// tracks changes to the effective value of this property.
/// </param>
private static void OnIsCaseSensitiveChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (HighlightBehavior)d;
bool oldIsCaseSensitive = (bool)e.OldValue;
bool newIsCaseSensitive = target.IsCaseSensitive;
target.OnIsCaseSensitiveChanged(oldIsCaseSensitive, newIsCaseSensitive);
}
/// <summary>
/// Provides derived classes an opportunity to handle changes
/// to the IsCaseSensitive property.
/// </summary>
/// <param name="oldIsCaseSensitive">The old IsCaseSensitive value</param>
/// <param name="newIsCaseSensitive">The new IsCaseSensitive value</param>
private void OnIsCaseSensitiveChanged(
bool oldIsCaseSensitive, bool newIsCaseSensitive)
{
UpdateHighlight();
}
#endregion
#region HighlightTemplate
/// <summary>
/// HighlightTemplate Dependency Property
/// </summary>
public static readonly DependencyProperty HighlightTemplateProperty =
DependencyProperty.Register(
"HighlightTemplate",
typeof(DataTemplate),
typeof(HighlightBehavior),
new PropertyMetadata(null, OnHighlightTemplateChanged));
/// <summary>
/// Gets or sets the HighlightTemplate property. This dependency property
/// indicates the template to use to generate the highlight Run inlines.
/// </summary>
public DataTemplate HighlightTemplate
{
get { return (DataTemplate)GetValue(HighlightTemplateProperty); }
set { SetValue(HighlightTemplateProperty, value); }
}
/// <summary>
/// Handles changes to the HighlightTemplate property.
/// </summary>
/// <param name="d">
/// The <see cref="DependencyObject"/> on which
/// the property has changed value.
/// </param>
/// <param name="e">
/// Event data that is issued by any event that
/// tracks changes to the effective value of this property.
/// </param>
private static void OnHighlightTemplateChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (HighlightBehavior)d;
DataTemplate oldHighlightTemplate = (DataTemplate)e.OldValue;
DataTemplate newHighlightTemplate = target.HighlightTemplate;
target.OnHighlightTemplateChanged(oldHighlightTemplate, newHighlightTemplate);
}
/// <summary>
/// Provides derived classes an opportunity to handle changes
/// to the HighlightTemplate property.
/// </summary>
/// <param name="oldHighlightTemplate">The old HighlightTemplate value</param>
/// <param name="newHighlightTemplate">The new HighlightTemplate value</param>
private void OnHighlightTemplateChanged(
DataTemplate oldHighlightTemplate, DataTemplate newHighlightTemplate)
{
UpdateHighlight();
}
#endregion
#region HighlightBrush
/// <summary>
/// HighlightBrush Dependency Property
/// </summary>
public static readonly DependencyProperty HighlightBrushProperty =
DependencyProperty.Register(
"HighlightBrush",
typeof(Brush),
typeof(HighlightBehavior),
new PropertyMetadata(new SolidColorBrush(Colors.Red), OnHighlightBrushChanged));
/// <summary>
/// Gets or sets the HighlightBrush property. This dependency property
/// indicates the brush to use to highlight the found instances of the search string.
/// </summary>
/// <remarks>
/// Note that the brush is ignored if HighlightTemplate is specified
/// </remarks>
public Brush HighlightBrush
{
get { return (Brush)GetValue(HighlightBrushProperty); }
set { SetValue(HighlightBrushProperty, value); }
}
/// <summary>
/// Handles changes to the HighlightBrush property.
/// </summary>
/// <param name="d">
/// The <see cref="DependencyObject"/> on which
/// the property has changed value.
/// </param>
/// <param name="e">
/// Event data that is issued by any event that
/// tracks changes to the effective value of this property.
/// </param>
private static void OnHighlightBrushChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = (HighlightBehavior)d;
Brush oldHighlightBrush = (Brush)e.OldValue;
Brush newHighlightBrush = target.HighlightBrush;
target.OnHighlightBrushChanged(oldHighlightBrush, newHighlightBrush);
}
/// <summary>
/// Provides derived classes an opportunity to handle changes
/// to the HighlightBrush property.
/// </summary>
/// <param name="oldHighlightBrush">The old HighlightBrush value</param>
/// <param name="newHighlightBrush">The new HighlightBrush value</param>
private void OnHighlightBrushChanged(
Brush oldHighlightBrush, Brush newHighlightBrush)
{
UpdateHighlight();
}
#endregion
private PropertyChangeEventSource<string> _textChangeEventSource;
/// <summary>
/// Called after the behavior is attached to an AssociatedObject.
/// </summary>
/// <remarks>
/// Override this to hook up functionality to the AssociatedObject.
/// </remarks>
protected override void OnAttached()
{
UpdateHighlight();
_textChangeEventSource = new PropertyChangeEventSource<string>(this.AssociatedObject, "Text", BindingMode.OneWay);
_textChangeEventSource.ValueChanged += TextChanged;
base.OnAttached();
}
/// <summary>
/// Called when the behavior is being detached from its AssociatedObject, but
/// before it has actually occurred.
/// </summary>
/// <remarks>
/// Override this to unhook functionality from the AssociatedObject.
/// </remarks>
protected override void OnDetaching()
{
ClearHighlight();
_textChangeEventSource.ValueChanged -= TextChanged;
_textChangeEventSource = null;
base.OnDetaching();
}
private void TextChanged(object sender, string s)
{
UpdateHighlight();
}
/// <summary>
/// Updates the highlight.
/// </summary>
public void UpdateHighlight()
{
if (this.AssociatedObject == null ||
string.IsNullOrEmpty(this.AssociatedObject.Text) ||
string.IsNullOrEmpty(this.SearchString))
{
ClearHighlight();
return;
}
var txt = this.AssociatedObject.Text;
var searchTxt = this.SearchString;
var processedCharacters = 0;
this.AssociatedObject.Inlines.Clear();
int pos;
while ((pos = txt.IndexOf(
searchTxt,
processedCharacters,
this.IsCaseSensitive ? StringComparison.CurrentCulture : StringComparison.CurrentCultureIgnoreCase)) >= 0)
{
if (pos > processedCharacters)
{
var run = new Run
{
Text =
txt.Substring(
processedCharacters, pos - processedCharacters)
};
this.AssociatedObject.Inlines.Add(run);
}
Run highlight;
var highlightText = txt.Substring(pos, searchTxt.Length);
if (this.HighlightTemplate == null)
{
highlight =
new Run
{
Text = highlightText,
Foreground = this.HighlightBrush
};
}
else
{
highlight = (Run)this.HighlightTemplate.LoadContent();
highlight.Text = highlightText;
}
this.AssociatedObject.Inlines.Add(highlight);
processedCharacters = pos + searchTxt.Length;
}
if (processedCharacters < txt.Length)
{
var run = new Run
{
Text =
txt.Substring(
processedCharacters, txt.Length - processedCharacters)
};
this.AssociatedObject.Inlines.Add(run);
}
}
/// <summary>
/// Clears the highlight.
/// </summary>
public void ClearHighlight()
{
if (this.AssociatedObject == null)
{
return;
}
var text = this.AssociatedObject.Text;
this.AssociatedObject.Inlines.Clear();
this.AssociatedObject.Inlines.Add(new Run{Text = text});
}
}
public abstract class Behavior<T> : Behavior where T : DependencyObject
{
#region Behavior() - CTOR
/// <summary>
/// Initializes a new instance of the <see cref="Behavior<T>"/> class.
/// </summary>
protected Behavior()
{
_associatedType = typeof(T);
}
#endregion
#region AssociatedObject
/// <summary>
/// Gets the object to which this <see cref="Behavior<T>" /> is attached.
/// </summary>
public new T AssociatedObject
{
get
{
return (T)_associatedObject;
}
internal set
{
_associatedObject = value;
}
}
#endregion
}
Best of luck!
I'm investigation the following NRE:
System.NullReferenceException: Object reference not set to an instance of an object.
at System.Collections.Generic.GenericEqualityComparer`1.GetHashCode(T obj)
at System.Collections.Generic.Dictionary`2.FindEntry(TKey key)
at System.Collections.Generic.Dictionary`2.ContainsKey(TKey key)
at UC4.Decision.Core.Event.EventObjectHeader.get_Item(String key)
...
My EventObjectHeader class looks like this:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.Serialization;
using System.Threading;
using UC4.Common.Utils.Xml;
namespace UC4.Decision.Core.Event
{
/// <summary>
/// Enumeration of the event priority.
/// </summary>
public enum EventPriority
{
/// <summary>
/// Event Priority Low
/// </summary>
Low = 0,
/// <summary>
/// Event Priority Medium (Default)
/// </summary>
Medium = 1,
/// <summary>
/// Event Priority High
/// </summary>
High = 2,
}
/// <summary>
/// Structure for the header information of an event object.
/// </summary>
/// <seealso cref="EventObject"/>
[Serializable]
public class EventObjectHeader : IEventObjectHeader, ISerializable
{
public const string KEY_TYPEURI = "typeUri";
/// <summary>
/// Fixed key which is used to store the predefined header element GUID.
/// </summary>
public const string KEY_GUID = "guid";
/// <summary>
/// Fixed key which is used to store the predefined header element IsPersistent.
/// </summary>
public const string KEY_ISPERSISTENT = "isPersistent";
/// <summary>
/// Fixed key which is used to store the time created in eventbase timezone of the Event.
/// </summary>
public const string KEY_TIME_CREATED = "timeCreated";
/// <summary>
/// Fixed key which is used to store the time created in UTC timezone of the Event.
/// </summary>
public const string KEY_TIME_CREATED_UTC = "timeCreatedUTC";
/// <summary>
/// Fixed key which is used to store the predefined header element Priority.
/// </summary>
public const string KEY_PRIORITY = "priority";
// internal data structure to store header elements as string tuples
private readonly IDictionary<string, string> _internal = new Dictionary<string, string>(6);
// lock for read and write requests to the internal dictionary
private readonly ReaderWriterLockSlim _internalDictionaryLock = new ReaderWriterLockSlim();
// caching dictionary
protected readonly Hashtable _cachedHeaderAttributes = Hashtable.Synchronized(new Hashtable());
/// <summary>
/// default header attributes (always loaded because they are columns of EB_EVENTS)
/// </summary>
public static readonly ICollection<String> DEFAULT_ATTRIBUTES = new List<string>(new[]
{
KEY_GUID, KEY_TIME_CREATED,
KEY_TIME_CREATED_UTC,KEY_TYPEURI
});
/// <summary>
/// Initializes a new instance of the <see cref="EventObjectHeader"/> class.
/// </summary>
public EventObjectHeader()
{
}
/// <summary>
/// Indicates whether the event is persistent
/// </summary>
public bool IsPersistent
{
get
{
if (ContainsKey(KEY_ISPERSISTENT) == false)
{
return false;
}
return GetValueAs<bool>(KEY_ISPERSISTENT);
}
set { SetValue(KEY_ISPERSISTENT, value); }
}
/// <summary>
/// Initializes a new instance of the <see cref="EventObjectHeader"/> class.
/// </summary>
/// <param name="eventObjectHeaderToCopy">The event object header to copy.</param>
private EventObjectHeader(IEventObjectHeader eventObjectHeaderToCopy) : this()
{
List<String> keys = new List<string>(eventObjectHeaderToCopy.Keys);
foreach (string key in keys)
{
Add(key, eventObjectHeaderToCopy[key]);
}
}
/// <summary>
/// Property for accessing the predefined header element GUID.
/// </summary>
public virtual Guid Guid
{
get
{
if (ContainsKey(KEY_GUID) == false)
{
return Guid.Empty;
}
return GetValueAs<Guid>(KEY_GUID);
}
set { SetValue(KEY_GUID, value); }
}
/// <summary>
/// Property for accessing the predefined header element LocalTimeCreated.
/// </summary>
public virtual DateTime TimeCreated
{
get
{
if (ContainsKey(KEY_TIME_CREATED) == false)
{
return default(DateTime);
}
return GetValueAs<DateTime>(KEY_TIME_CREATED);
}
set { SetValue(KEY_TIME_CREATED, value); }
}
/// <summary>
/// Property for accessing the predefined header element UTC Time created
/// </summary>
public virtual DateTime TimeCreatedUTC
{
get
{
if (ContainsKey(KEY_TIME_CREATED_UTC) == false)
{
return default(DateTime);
}
return GetValueAs<DateTime>(KEY_TIME_CREATED_UTC);
}
set { SetValue(KEY_TIME_CREATED_UTC, value); }
}
/// <summary>
/// Property for accessing the predefined header element Priority.
/// </summary>
public virtual EventPriority Priority
{
get
{
if (ContainsKey(KEY_PRIORITY) == false)
{
return EventObjectHeaderHelper.DEFAULT_PRIORITY;
}
return GetValueAs<EventPriority>(KEY_PRIORITY);
}
set { SetValue(KEY_PRIORITY, value); }
}
/// <summary>
/// Make a copy of the event object header instance.
/// </summary>
/// <returns>New instance with the same values</returns>
public virtual EventObjectHeader Copy()
{
return new EventObjectHeader(this);
}
/// <summary>
/// Clear the event object header.
/// </summary>
public virtual void Clear()
{
_internalDictionaryLock.EnterWriteLock();
try
{
_internal.Clear();
_cachedHeaderAttributes.Clear();
}
finally
{
_internalDictionaryLock.ExitWriteLock();
}
}
/// <summary>
/// Access the stored values via the key.
/// </summary>
/// <param name="key">Attribute key</param>
/// <returns>Value</returns>
public virtual string this[string key]
{
set
{
_internalDictionaryLock.EnterWriteLock();
try
{
_internal[key] = value;
if (_cachedHeaderAttributes.ContainsKey(key))
{
_cachedHeaderAttributes.Remove(key);
}
}
finally
{
_internalDictionaryLock.ExitWriteLock();
}
}
get
{
_internalDictionaryLock.EnterReadLock();
try
{
if (_internal.ContainsKey(key))
{
return _internal[key];
}
}
finally
{
_internalDictionaryLock.ExitReadLock();
}
if (key == KEY_ISPERSISTENT)
{
// this is to prevent a Exception when the IsPersistent attribute
// hadn't been set in advance
return XmlHelper.XmlConvertValueToString(false);
}
throw new EventObjectException(1167, key);
}
}
/// <summary>
/// Remove a stored value by its key.
/// If the key was not found, <code>null</code> is returned.
/// </summary>
/// <param name="key">Attribute key</param>
/// <returns>The value of the element which was removed</returns>
public virtual string Remove(string key)
{
string retVal;
_internalDictionaryLock.EnterWriteLock();
try
{
_internal.TryGetValue(key, out retVal);
_internal.Remove(key);
if (_cachedHeaderAttributes.ContainsKey(key))
{
_cachedHeaderAttributes.Remove(key);
}
}
finally
{
_internalDictionaryLock.ExitWriteLock();
}
return retVal;
}
/// <summary>
/// Get a collection of the stored keys inside the header.
/// </summary>
public virtual ICollection<string> Keys
{
get
{
_internalDictionaryLock.EnterReadLock();
try
{
return _internal.Keys;
}
finally
{
_internalDictionaryLock.ExitReadLock();
}
}
}
/// <summary>
/// Check if the header contains a specific element.
/// </summary>
/// <param name="key">Attribute key</param>
/// <returns>true, if the header contains a value for this key</returns>
public virtual bool ContainsKey(string key)
{
_internalDictionaryLock.EnterReadLock();
bool retVal;
try
{
retVal = _internal.ContainsKey(key);
}
finally
{
_internalDictionaryLock.ExitReadLock();
}
return retVal;
}
/// <summary>
/// Read only property which counts the elements in the header.
/// </summary>
public virtual int Count
{
get
{
_internalDictionaryLock.EnterReadLock();
int retVal;
try
{
retVal = _internal.Count;
}
finally
{
_internalDictionaryLock.ExitReadLock();
}
return retVal;
}
}
/// <summary>
/// Get a stored element casted to a specific type.
/// if the value is not found, the default of this type is returned.
/// </summary>
/// <typeparam name="T">Runtime type of the value</typeparam>
/// <param name="key">Attribute key</param>
/// <returns>RuntimeType safe value</returns>
public virtual T GetValueAs<T>(String key)
{
try
{
if (_cachedHeaderAttributes.ContainsKey(key))
{
Object obj = _cachedHeaderAttributes[key];
if (obj is T)
{
return (T) obj;
}
}
String value = this[key];
return XmlHelper.XmlConvertStringToValue<T>(value);
}
catch (TargetInvocationException e)
{
throw e.InnerException;
}
}
/// <summary>
/// Add a new element with a specific key.
/// </summary>
/// <param name="key">Attribute key</param>
/// <param name="value">Value</param>
public virtual void Add(String key, String value)
{
Add(key, value, false);
}
/// <summary>
/// Add a new element with a specific key.
/// </summary>
/// <typeparam name="T">Optional runtime type parameter</typeparam>
/// <param name="key">Attribute key</param>
/// <param name="value">Value</param>
public virtual void Add<T>(String key, T value)
{
Add(key, value, false);
}
/// <summary>
/// Set a value for a specific key.
/// If the key was already used, the value is overwritten and the old one is returned.
/// </summary>
/// <typeparam name="T">Optional runtime type parameter</typeparam>
/// <param name="key">Attribute key</param>
/// <param name="value">Value</param>
/// <returns>The old value if it exists</returns>
public virtual string SetValue<T>(String key, T value)
{
string retVal = null;
_internalDictionaryLock.EnterWriteLock();
try
{
if (_internal.ContainsKey(key))
{
retVal = _internal[key];
}
_internal.Remove(key);
_internal.Add(key, XmlHelper.XmlConvertValueToString(value));
_cachedHeaderAttributes[key] = value;
}
catch (TargetInvocationException e)
{
throw e.InnerException;
}
finally
{
_internalDictionaryLock.ExitWriteLock();
}
return retVal;
}
// generic add value to the header
protected virtual void Add<T>(String key, T value, bool overwrite)
{
_internalDictionaryLock.EnterWriteLock();
try
{
if (overwrite)
{
_internal.Remove(key);
}
_internal.Add(key, XmlHelper.XmlConvertValueToString(value));
_cachedHeaderAttributes[key] = value;
}
catch (TargetInvocationException e)
{
throw e.InnerException;
}
finally
{
_internalDictionaryLock.ExitWriteLock();
}
}
#region ISerializable Members
/// <summary>
/// Create a new instance from the serialization stream.
/// </summary>
/// <param name="info">Serializer information</param>
/// <param name="context">Serialization stream context</param>
protected EventObjectHeader(SerializationInfo info, StreamingContext context)
{
_internal =
info.GetValue("headerAttributes", typeof (Dictionary<string, string>)) as Dictionary<string, string>;
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("headerAttributes", _internal);
}
#endregion
}
}
All access to the _internal dictionary is protected by a ReaderWriterLockSlim - thus I can't see any possible concurrency issue.
With dotPeek I digged into the Dictionary class. Its ContainsKey method just returns this.FindEntry(key) >= 0, the FindEntry method looks like this:
private int FindEntry(TKey key)
{
if ((object) key == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
if (this.buckets != null)
{
int num = this.comparer.GetHashCode(key) & int.MaxValue;
for (int index = this.buckets[num % this.buckets.Length]; index >= 0; index = this.entries[index].next)
{
if (this.entries[index].hashCode == num && this.comparer.Equals(this.entries[index].key, key))
return index;
}
}
return -1;
}
And the GenericEqualityComparer's GetHashCode method looks like that:
public override int GetHashCode(T obj)
{
if ((object) obj == null)
return 0;
else
return obj.GetHashCode();
}
In the end, all I'm doing is a simple ContainsKey on a Dictionary<string, string>. I have no explanation for the NRE, but it seems to happen regularly in one of our customer's productive system. What could possibly explain the NullReferenceException?