C# a Step by Step Beginners Tutorial

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts

  • Drifter
    replied
    You kidding?

    No, seriously. I have a bot up and running already, but my background in scripting and C really doesn't help me when it comes to Object oriented methods. The 'subscriber' model you are using is the sort of thing I would like to be able to get to myself but really struggle. I don't deny there are odd things I might do differently - but that isn't the point. Your development isn't helping me access Betfair (I already do that) or even teaching me to program (likewise). But it is helping me use the API much better than I could do with my existing skillset. I also think it is very useful to have a C# model next to the VB one on the other thread. Structured methods are only lightly addressed over there and as for threading, events, delegates, OO design principles...


    Keep up the good work.

    PS - I'm also interested in seeing a database access strategy (whether persistent or not) and a way of addressing and passing market information on multiple EventID's to multiple separate client forms - if it fits in with your approach, and of course if you have the time and interest! Your posts so far have already enhanced my appreciation of what C#/.Net brings me, and I look forward to more of the same!
    Last edited by Drifter; 18-07-2012, 12:07 AM.

    Leave a comment:


  • Escapee
    replied
    wow, come back after a few days break in europe and very pleased to see a few screen shots....


    twisterpb
    "when i look at the values on the webpage for an event and look into my code, the values are mostly not the same"

    This is probably because of what is known as "Betfairs X-Matching Bot'". which generates synthetic odds for the web page but not the API.
    See This thread for more info.


    Rui Rodrigues

    "NullReferenceException was unhandled - Object reference not set to an instance of an object"
    Have you completed upto step 73 ?
    because the null reference problem is caused by not redirecting MenuTree.usrmsg

    have a look at step 73, your form1.cs constructor should contain the redirection code
    Code:
    c_MenuTree.MsgRedirect( this.UsrMsg );
    I'm less certain, but this may also be the cause of your lock( e_mData.varsLock ) problem.


    Drifter and AzureGulf, thanks for the feedback and encouragement makes me feel I'm not wasting my time and I'll carry on.

    part 7 hopefully end of this week or early next week.

    Leave a comment:


  • Drifter
    replied
    Rui - there are small (and I emphasise small) coding leaps you need to make. Whether Escapee has missed a couple of things deliberately, or just in transcription isn't really important. You can see that at least 3 of us managed to get there, and I would suggest there is more to be gained by chasing down the error message than being led blindly. If you are *really* stuck, then I am sure someone will respond if you post again - but if you can't solve the small problems that are there, you may struggle to actually apply any of this.

    Leave a comment:


  • Rui Rodrigues
    replied
    After Step 62 I get an error at MenuTree.cs at this line:

    UsrMsg( string.Format( "User Clicked the Market: ({0}), ID = ({1}), ", tNode.Text, tNode.m_id ) );

    NullReferenceException was unhandled - Object reference not set to an instance of an object

    If I take de "UsrMsg" and return to "Debug.WriteLine" the error is ignored and another will appear at

    lock( e_mData.varsLock )


    Everything compiled just fine until now... Any thoughts?


    BTW also had to add :

    using BetfairAPI.BFGlobal;
    using BetfairAPI.BFExchange;


    to CMarketData.cs so I wouldn't get errors concerning

    GetMarketResp
    GetMarketPricesResp
    GetMarketPricesCompressedResp


    Found it odd as it is in the same name space But this way it works


    Any comments will be most appreciated.

    Leave a comment:


  • AzureGulf
    replied
    Me 2!

    As always, Escapee, thanks for all the hard work and excellent thread.

    As you'll see from the image, I started following your tutorial during Euro2012, but have been side-tracked into developing a "bot" that will monitor Soccer matches. It's progressing well, based upon your initial 'menu-tree' foundations - right now, I'm getting to grips with Entity Framework as the 'model' to the database...but, I keep on learning from your posts, so thanks again

    Leave a comment:


  • Drifter
    replied
    Likewise

    All going well over here too.



    Last edited by Drifter; 15-07-2012, 10:10 PM.

    Leave a comment:


  • twisterpb
    replied
    first, big thanks for your work!!!


    (sorry for the german text )


    yes i can run the program.

    For me the important part is coming with part 7 and further
    The price runners objects making me crazy!

    when i look at the values on the webpage for an event and look into my code, the values are mostly not the same

    Leave a comment:


  • Escapee
    replied
    Part 6.2

    Part 6 Continued...

    At this point we have functionality in the CBetfairAPI class to retieve market and price data from betfair,
    and the functionality in the CMarketData class to process and store the betfair data.

    With this functionality, we can make our MenuTree behave like the standard betfair web MenuTree.
    i.e. when a user clicks on a market we want it to call GetMarket() and GetMarketPrices() for the
    relevant market, and then store the returned data ready for the data consumers to display or process
    further.


    Step 60
    In the MenuTree.cs file, rename m_betfair to e_betfair ( the 'e_' prefix denotes variables
    that are instantiated externally to the class )
    and then change and or add the code so that it looks like this:
    Code:
    .....
    .....
    using System.Diagnostics;
    
    using BetfairAPI;
    using BetfairAPI.BFGlobal;
    using BetfairAPI.BFExchange;
    
    
    namespace BetfairControls
    {
        public partial class MenuTree : TreeView
        {
            public MenuTree()
            {
                InitializeComponent();
    
                [COLOR="Red"]e_Betfair[/COLOR] = null;
                e_mData = null;
            }
            public CBetfairAPI e_Betfair;
    
            public CMarketData e_mData;
    
            public delegate void NewMarketHandler( object sender, EventArgs e );
            public event NewMarketHandler evNewMarket;
    
            #region UsrMsg
    .....
    .....
    Step 61
    Locate the OnAfterSelect( TreeViewEventArgs e ) function and scroll down to case "MarketSummary":
    and add the code to process when a user clicks a MarketSummary entry on the menu.
    Code:
        case "MarketSummary":
    
    
            UsrMsg( string.Format( "User Clicked the Market: ({0}), ID  = ({1}), ", tNode.Text, tNode.m_id ) );
    
            GetMarketResp marketResp = new GetMarketResp();
            GetMarketPricesResp marketPricesResp = new GetMarketPricesResp();
    
            if( e_Betfair.GetMarket( ref marketResp, tNode.m_id ) == false )
            {
                UsrMsg( "OnAfterSelect()->GetMarket() Returned false ?!" );
                return;
            }
    
    
            if( e_Betfair.GetMarketPrices( ref marketPricesResp, tNode.m_id ) == false )
            {
                UsrMsg( "OnAfterSelect()->GetMarketPrices() Returned false ?!" );
                return;
            }
    
            lock( e_mData.varsLock )
            {
                e_mData.LoadNewMarket( ref marketResp );
    
                e_mData.UpdateMarketPrices( ref marketPricesResp );
    
                if( evNewMarket != null )
                {
                    evNewMarket( this, EventArgs.Empty );
                }
            }
    
    
            break;
    save and compile to check everything is OK

    At this point, the menu works and data is stored/changed when the user clicks on a market.
    But it doesn't refresh ! ( or display, see part 7 ) and so the price data rapidily becomes
    old and useless.

    what we need is something ticking away in the background, invisible to the user which looks
    at the current market ID selected by the user and refreshes the price data ( and bet data later )
    every second or so.... i.e. the Refresh Control or "Data Producer"

    INTERLUDE

    Events, what ? why ?

    Events, ( in earlier decades known as triggers, or messages. now called Events ) are a way
    of broadcasting, to whatever is interested, a little message saying "This Just Happened"

    Again, like delegates, they are a way of crossing boundaries, with out either side of the
    boundary knowing or caring about the other side.

    events are often modeled as 'publisher' and 'subscribers' as an event can trigger many or
    zero actions.

    a button is a classic example of event processing,
    The guy who coded that button has no idea what you want it to do when the user clicks it,
    his remit mostly pertained to detecting when the mouse pointer is over the button, and
    drawing different bitmaps giving the human the impression that a button has actually moved
    and depressed when you click it.

    As he doesn't know what you want to do when its pressed, all he does is publish (broadcast) a message
    indicating the event just occurred. i.e. he's programmed upto his boundary and he doesn't care
    about what happens to this message.

    On the other side of the button boundary is you, you don't know or care about the coding and processing
    required to draw the button or detect the mouse position or clicks, all you want to know is when the
    button was clicked so you can act upon it.
    So all you do is subscribe to this event, which is a way of coding 'When event occurs, run this code'

    we are going to use this architecture for our GUI refresh control.
    i.e. we're going to create a thread which cycles every second or so, refreshing the data.
    And after each data snapshot is created it'll broadcast a custom 'MarketUpdated' event.
    Anything that is interested in displaying or processing that data just subscribes to the
    'MarketUpdated' event.

    if you're still unclear about events, create yourself a blank form project and add a button to it.
    add the OnClick event to the button then view all the code generated ( search for Click ).

    This blog entry has a good example of coding a custom event and better explanation of
    the conventions: Link

    INTERLUDE ENDS



    A NumericUpDown Control is used to display and adjust the frequency of the refreshes.
    A thread and functionality to start/stop the thread is added to this control, and its
    the thread which cycles in the background refreshing the data and firing a custom
    'MarketUpdated' event once per cycle at the appropiate time.

    Step 62
    Right Click BetfairControls, ADD->UserControl... rename it to RefreshControl... ADD.
    Step 62a
    in RefreshControl.Designer.cs, Comment out the AutoScaleMode

    Step 63
    in RefreshControl.cs, change UserControl to NumericUpDown like this:
    Code:
    namespace BetfairControls
    {
        public partial class RefreshControl : [COLOR="Red"]NumericUpDown[/COLOR]
        {
            public RefreshControl()
            {
                InitializeComponent();
            }
        }
    }
    Step 64
    Add this to below the existing using statements
    Code:
    using System.Diagnostics;
    using System.Threading;
    
    using BetfairAPI;
    using BetfairAPI.BFExchange;
    using BetfairAPI.BFGlobal;
    Then add the Standard UsrMsg Code, copy it from CMarketData.cs ( see step 55 for more info )


    step 65

    Above the UsrMsg code, add the variables:
    Code:
            #region Variables
    
            private CBetfairAPI _Betfair;
    
            private string _uuu;
            private string _ppp;
    
            public CMarketData e_mData;
    
            public delegate void MarketUpdatedHandler( object sender, EventArgs e );
            public event MarketUpdatedHandler evMarketUpdated;
    
            volatile object varsLock = new object();
            public int mSecs
            {
                get
                {
                    lock( varsLock ) { return (int)( Value * 1000 ); }
                }
                set
                {
                    lock( varsLock ) { Value = value / 1000; }
                }
            }
    
            Thread trdAutoRefresh;
            private volatile bool _bStopThread;
            private volatile bool _bThreadStopped;
    
            #endregion
    Step 66
    Above the variables, change the constructor and add a destructor so it looks like this
    Code:
            public RefreshControl()
            {
                InitializeComponent();
    
                UsrMsg = new UsrMsgDelegate( this.TraceMsg );
    
                this.TextAlign = HorizontalAlignment.Center;
                this.Minimum = 1;
                this.Maximum = 15;
            }
            ~RefreshControl()
            {
                StopAutoRefresh();
            }
    Step 67
    Finally, below the UsrMsg code, add the AutoRefresh thread code and the functions which start and stop the thread.
    Code:
            #region Functions
    
            public void InitVars(  string uuu, string ppp )
            {
                _uuu = uuu;
                _ppp = ppp;
            }
    
            public void StartAutoRefresh()
            {
                if( trdAutoRefresh == null || _bThreadStopped == true )
                {
                    trdAutoRefresh = null;
    
                    _bThreadStopped = false;
                    _bStopThread = false;
    
                    TraceMsg( "Starting AutoRefresh Thread" );
    
                    trdAutoRefresh = new Thread( new ThreadStart( AutoRefresh ) );
                    trdAutoRefresh.IsBackground = true;
                    trdAutoRefresh.Name = "AutoRefresh";
                    trdAutoRefresh.Start();
                }
            }
            public void StopAutoRefresh()
            {
                if( trdAutoRefresh != null )
                {
                    _bStopThread = true;  // try an orderly stop
    
                    TraceMsg( "Stopping AutoRefresh Thread" );
    
                    Thread.Sleep( 300 );
    
                    if( _bThreadStopped == false )
                    {
    			 	// Orderly stop failed so abort() it 
                        TraceMsg( "StopAutoRefresh() AutoRefresh Thread Still Going !? Issuing Abort()" );
    
                        trdAutoRefresh.Abort();
    
                        Thread.Sleep( 100 );
                    }
    
                    trdAutoRefresh = null;
                }
            }
    
            private void AutoRefresh()
            {
                int mID;
    
                int waitMsec;
                int sleepMsec;
    
                Stopwatch sw = new Stopwatch();
    
                GetMarketPricesCompressedResp MPCResp = new GetMarketPricesCompressedResp();
    
                if( _Betfair != null )
                {
                    _Betfair = null;
                }
    
                _Betfair = new CBetfairAPI();
    
                _Betfair.MsgRedirect( this.UsrMsg );
    
                if( _Betfair.Login( _uuu, _ppp ) != true )
                {
                    UsrMsg( "AutoRefresh() Error: Logon to Betfair Failed" );
    
                    e_mData.errorDescription += "(AutoRefresh FAILED TO LOGON), ";
                    e_mData.bError = true;
    
                    _bThreadStopped = true;
    
                    return;
                }
    
                while( _bStopThread == false )
                {
    
                    if( e_mData.ID <= 0 )
                    {
                        Thread.Sleep( 1000 );
    
                        continue;
                    }
    
                    mID = e_mData.ID;
    
                    sw.Reset();
                    sw.Start();
    
    
                    if( _Betfair.GetMktPricesCompressed( ref MPCResp, mID ) != true )
                    {
                        if( e_mData.errorDescription.IndexOf( "(AutoRefesh Get Market Prices Failed)" ) < 0 )
                        {
    
                            UsrMsg( "ERROR:   AutoRefresh()->GetMktPricesCompressed()  returned false" );
                            UsrMsg( "ERROR:   STOP BETTING and Reconcile Your Position Manually..... ASAP URGENTLY !" );
    
                            e_mData.bError = true;
    
                            e_mData.errorDescription += "(AutoRefesh Get Market Prices Failed), ";
    
                            TraceMsg( e_mData.errorDescription );
                        }
    
                        UsrMsg( "ERROR:   Updating Market Prices FAILED,  AutoRefresh Stopping" );
    
                        _bThreadStopped = true;
    
                        return;
                    }
    
                    if( _bStopThread == false )
                    {
                        lock( e_mData.varsLock )
                        {
                            if( e_mData.UpdateMarketPricesCompressed( ref MPCResp ) == false )
                            {
                                Thread.Sleep( mSecs );
    
                                continue;
                            }
                        }
                    }
    
                    if( _bStopThread == false )
                    {
                        if( evMarketUpdated != null )
                        {
                            evMarketUpdated( this, EventArgs.Empty );
                        }
                    }
    
                    sw.Stop();
    
                    waitMsec = mSecs - (int)sw.ElapsedMilliseconds;
                    if( waitMsec < 40 )
                    {
                        waitMsec = 40;
                    }
    
                    for( int c = 0; c < waitMsec; c += 200 )
                    {
                        if( _bStopThread == true )
                        {
                            break;
                        }
    
                        sleepMsec = waitMsec - c;
    
                        if( sleepMsec > 200 )
                        {
                            sleepMsec = 200;
                        }
    
                        Thread.Sleep( sleepMsec );
                    }
    
                }
    
                _bThreadStopped = true;
                return;
            }
    
            #endregion
    save and compile to check everything is OK

    If all is hunky dory we can proceed to adding our new RefreshControl to the Form
    along with a couple of eBox's to display some information.


    Step 68

    In the Form1 designer, enlarge the Form so there's some space to put more controls.

    You should also now see an entry for 'RefreshControl' in the toolbox under 'BetfairControls Components'

    Add an eBox to the form and set the properties too:
    Name = eMktDesc
    Multiline = true;
    ReadOnly = true;
    Change the Font Properties from size 8 regular to size 10 bold.
    Now resize eMktDesc so that its about 200,120 in size

    Step 69
    Add another eBox to the form and set the properties too:
    Name = eLastUpdate
    ReadOnly = true;

    Step 70
    Add a RefreshControl to the form, set the properties too:
    Name = c_Refresh
    TextAlign = center
    Value = 1

    After adding those controls your form should look like this ( minus the red writing ofcourse )




    At this point all we have to do is add the code to the form which initialises and hooks up the
    controls so that they dance as required.

    Step 71
    Add the functions we want to run when the evNewMarket or evMarketUpdated events are fired.

    In Form1.cs, below the ButLogOn_Click() function add:
    Code:
            private void OnNewMarket( object sender, EventArgs e )
            {
                eMktDesc.SetText( mData.mktDisplayName );
    
                OnMarketUpdated(  sender,  e );
            }
    
            private void OnMarketUpdated( object sender, EventArgs e )
            {
                string sTmp = string.Format( "Last Updated: {0}", mData.lastUpdated.ToLocalTime().ToString( "HH:mm:ss.f" ) );
    
                eLastUpdate.SetText( sTmp );
            }
    Step 72
    In the ButLogon_Click() function add the lines marked in RED
    Code:
                    if( MyBetfair.Login( uuu, ppp ) == true )
                    {
                        eUsr.SetText( "UserName" );
                        ePwd.SetText( "Password" );
    
                        eUsr.Visible = false;
                        ePwd.Visible = false;
    
                        bLoggedOn = true;
    
                        c_MenuTree.Initialise();
    
                        ButLogOn.Text = "Log Out";
    
    [COLOR="Red"]                    c_Refresh.InitVars( uuu, ppp );
                        c_Refresh.StartAutoRefresh();[/COLOR]
    
                        UsrMsg( "Logged On OK" );
    
                    }
    And also the Line to stop AutoRefresh() here
    Code:
                    if( MyBetfair.LogOut() == true )
                    {
                        bLoggedOn = false;
    
                        ButLogOn.Text = "Log On";
    
                        UsrMsg( "Logged Out of Betfair" );
                    }
                    else
                    {
                        UsrMsg( "Log Out FAILED  !?" );
                    }
    
                   [COLOR="Red"] c_Refresh.StopAutoRefresh();[/COLOR]
    
                    eUsr.Visible = true;
                    ePwd.Visible = true;
    Step 73
    Finally, In the Form1 constructor, subscribe the event handlers to the events so it looks
    like this:

    Code:
            public CBetfairAPI MyBetfair;
            public bool bLoggedOn;
    
            public CMarketData mData;
    
            public Form1()
            {
                InitializeComponent();
    
                MyBetfair = new CBetfairAPI();
                MyBetfair.MsgRedirect( this.UsrMsg );
    
                mData = new CMarketData();
                mData.MsgRedirect( this.UsrMsg );
    
                c_Refresh.e_mData = mData;
                c_Refresh.evMarketUpdated += new BetfairControls.RefreshControl.MarketUpdatedHandler( this.OnMarketUpdated );
                c_Refresh.MsgRedirect( this.UsrMsg );
    
                c_MenuTree.e_Betfair = MyBetfair;
                c_MenuTree.e_mData = mData;
                c_MenuTree.evNewMarket += new BetfairControls.MenuTree.NewMarketHandler( this.OnNewMarket );
                c_MenuTree.MsgRedirect( this.UsrMsg );
    
                bLoggedOn = false;
            }
    Save, Compile and RUN !

    Login, Navigate through the menu until you have selected a market and you should have something like
    this



    You should notice the 'LastUpdated' time ticking away, this proves the AutoRefresh thread is alive
    and working away in the background.
    You can adjust the refresh cycle time using the up/down control.

    Step 74
    Has anyone completed all the steps so far ?
    Take a screen print ( ctrl+alt+PrtSc ) paste into 'paint', save it, upload it to www.postimage.org,
    then post it into this thread using the 'insert image' button.
    Thankyou.


    This to come in Part 7

    we create a custom control to display the market prices which are now being automatically
    refreshed by the newly built RefreshControl every second.
    Last edited by Escapee; 13-07-2012, 05:50 PM.

    Leave a comment:


  • Escapee
    replied
    Part 6

    Part 6

    The Refresh Control ( A.K.A The Data Producer )
    Some Market Information Display eBoxes ( i.e. some of the minor Data Consumers )

    In this chapter we build the control class that is responsible for updating the Market Memory
    every N seconds. This comprises of standard "NumericUpDown" control combined with a thread and
    the functionality to start and stop the thread.

    And also we add the eBox's to the form which display the market description and when the
    market prices were last updated.


    If you're a novice c# programmer, then you'll also learn about custom events and multithreading
    in this part.



    First off, we're going to extend the CBetfairAPI functionality and add code to obtain the market and
    price data.
    Step 58
    In CBetfairAPI.cs, add the following functions:
    Code:
            public bool GetMarket( ref GetMarketResp marketResp, int mID )
            {
                bool bRetCode;
                const string serviceName = "GetMarketReq";
    
                var request = new GetMarketReq();
    
    
                request.header = _exchReqHdr;
                request.marketId = mID;
    
                marketResp = _bfExchange.getMarket( request );
    
                bRetCode = CheckResponse( serviceName,
                                            Convert.ToString( marketResp.header.errorCode ),
                                            Convert.ToString( marketResp.errorCode ),
                                            marketResp.header.sessionToken );
    
                if( bRetCode == false )
                {
                    return false;
                }
    
                return true;
            }
    
            public bool GetMarketPrices( ref GetMarketPricesResp marketPricesResp, int mID )
            {
                bool bRetCode;
                const string serviceName = "getMarketPrices";
    
    
                var request = new GetMarketPricesReq();
    
                request.header = _exchReqHdr;
                request.marketId = mID;
                request.currencyCode = _currency;
    
                marketPricesResp = _bfExchange.getMarketPrices( request );
    
    
                bRetCode = CheckResponse( serviceName,
                                            Convert.ToString( marketPricesResp.header.errorCode ),
                                            Convert.ToString( marketPricesResp.errorCode ),
                                            marketPricesResp.header.sessionToken );
    
                if( bRetCode == false )
                {
                    return false;
                }
    
                return true;
            }
            public bool GetMktPricesCompressed( ref GetMarketPricesCompressedResp MPCResp, int mID )
            {
                bool bRetCode;
                const string serviceName = "getMarketPricesCompressed";
    
    
                var request = new GetMarketPricesCompressedReq();
    
                request.header = _exchReqHdr;
                request.marketId = mID;
    
                MPCResp = _bfExchange.getMarketPricesCompressed( request );
    
                bRetCode = CheckResponse( serviceName,
                                            Convert.ToString( MPCResp.header.errorCode ),
                                            Convert.ToString( MPCResp.errorCode ),
                                            MPCResp.header.sessionToken );
    
                if( bRetCode == false )
                {
                    return false;
                }
    
                return true;
            }

    Now that the CBetfairAPI class has the functionality to obtain Market and Market Prices data,
    we need to create some functionality to store this data in an orderly and easily accessible fashion.
    CMarketData is where it's going to be stored, so this is where we add the functions: LoadNewMarket(),
    UpdateMarketPrices() and UpdateMarketPricesCompressed().
    These functions take the 'response' object returned by betfair in response to a 'get' call as
    a parameter. Each function is tailored to process its response object, extracting and storing
    the pertinent data.

    Step 59
    add the following functions to the CMarketData.cs class file
    Code:
            #region Functions to load and clear the Market and Runner data
    
            public void Clear()
            {
                ID = 0;
                nRunners = 0;
                nWinners = 0;
                name = "";
                desc = "";
                bspMarket = false;
                eventTypeID = 0;
                RDATA = null;
    
            }
    Code:
            public void LoadNewMarket( ref GetMarketResp marketResp )
            {
                int r;
                int p;
    
                lock( varsLock )
                {
                    Clear();
    
    
                    ID = marketResp.market.marketId;
                    nRunners = marketResp.market.runners.Length;
                    nWinners = marketResp.market.numberOfWinners;
                    name = marketResp.market.name;
                    desc = marketResp.market.marketDescription;
                    bspMarket = marketResp.market.bspMarket;
                    eventTypeID = marketResp.market.eventTypeId;
                    startTime = marketResp.market.marketTime;
    
                    menuPath = marketResp.market.menuPath;
    
                    if( marketResp.market.marketDisplayTime.ToLocalTime().ToString( "dd-MMM-yyyy" ) != "01-Jan-0001" )
                    {
                        if( nWinners > 1 )
                        {
                            mktDisplayName = String.Format( "{0}\r\n{1}  {2}\r\n{3} Runners,  {4} Winners", menuPath.Replace( '\\', ' ' ), marketResp.market.marketDisplayTime.ToLocalTime().ToString( "HH:mm" ), name, nRunners, nWinners );
                        }
                        else
                        {
                            mktDisplayName = String.Format( "{0}\r\n{1}  {2}\r\n{3} Runners,  Win Only", menuPath.Replace( '\\', ' ' ), marketResp.market.marketDisplayTime.ToLocalTime().ToString( "HH:mm" ), name, nRunners );
                        }
    
                    }
                    else
                    {
    
                        if( nWinners > 1 )
                        {
                            mktDisplayName = String.Format( "{0}\r\n{1}\r\n{2} Selections,  {3} Winners", menuPath.Replace( '\\', ' ' ), name, nRunners, nWinners );
                        }
                        else
                        {
                            mktDisplayName = String.Format( "{0}\r\n{1}\r\n{2} Selections,  Win Only", menuPath.Replace( '\\', ' ' ), name, nRunners );
                        }
    
                    }
                    mktDisplayName = mktDisplayName.TrimStart( ' ' );
    
                    RDATA = new CRunnerData[marketResp.market.runners.Length];
    
                    if( marketResp.market.runners == null )
                    {
                        return;
                    }
    
                    r = 0;
                    foreach( Runner rnr in marketResp.market.runners )
                    {
                        RDATA[r] = new CRunnerData();
    
                        RDATA[r].Name = rnr.name;
                        RDATA[r].ID = rnr.selectionId;
    
                        RDATA[r].Handicap = rnr.handicap;
                        RDATA[r].AsianLineID = rnr.asianLineId;
    
                        RDATA[r].GridRow = r + 1;
    
                        for( p = 0; p < 3; p++ )
                        {
                            RDATA[r].BackPrice[p].dAmount = 0;
                            RDATA[r].BackPrice[p].dOdds = 0;
                            RDATA[r].LayPrice[p].dAmount = 0;
                            RDATA[r].LayPrice[p].dOdds = 0;
                        }
    
    
                        r++;
                    }
                }
    
            }

    Code:
            public bool UpdateMarketPrices( ref GetMarketPricesResp marketPricesResp )
            {
                int i;
                int rInd;
    
                lock( varsLock )
                {
    
                    if( marketPricesResp.marketPrices.marketId != ID )
                    {
                        return false;
                    }
    
                    dBackPercent = 0;
                    dLayPercent = 0;
    
    
                    delay = marketPricesResp.marketPrices.delay;
    
                    status = Convert.ToString( marketPricesResp.marketPrices.marketStatus );
    
                    info = marketPricesResp.marketPrices.marketInfo;
    
                    
                    lastUpdated = marketPricesResp.header.timestamp;
    
                    removedRunners = marketPricesResp.marketPrices.removedRunners;
    
                    if( nRunners == 0 )
                    {
                        return ( false );
                    }
    
    
                    if( marketPricesResp.marketPrices.runnerPrices == null )
                    {
                        return ( false );
                    }
    
                    i = 0;
                    foreach( RunnerPrices rp in marketPricesResp.marketPrices.runnerPrices )
                    {
                        if( rp.selectionId == RDATA[i].ID )
                        {
                            rInd = i;
                            i++;
                        }
                        else
                        {
                            if( ( rInd = getIndexFromID( rp.selectionId ) ) == -1 )
                            {
                                TTraceMsg( "Error: UpdateMarketPrices() Could not match RunnerID ({0}) with known Market Runners !?", rp.selectionId );
                                return false;
                            }
                        }
    
                        if( rp.bestPricesToBack != null )
                        {
                            foreach( Price pArr in rp.bestPricesToBack )
                            {
                                RDATA[rInd].BackPrice[( pArr.depth - 1 )].dOdds = pArr.price;
                                RDATA[rInd].BackPrice[( pArr.depth - 1 )].dAmount = pArr.amountAvailable;
    
                            }
                        }
                        if( rp.bestPricesToLay != null )
                        {
                            foreach( Price pArr in rp.bestPricesToLay )
                            {
                                RDATA[rInd].LayPrice[( pArr.depth - 1 )].dOdds = pArr.price;
                                RDATA[rInd].LayPrice[( pArr.depth - 1 )].dAmount = pArr.amountAvailable;
    
                            }
                        }
    
                        dBackPercent += ( 100 / RDATA[rInd].BackPrice[0].dOdds );
                        dLayPercent += ( 100 / RDATA[rInd].LayPrice[0].dOdds );
                    }
                }
                return ( true );
            }
    Code:
            public bool UpdateMarketPricesCompressed( ref GetMarketPricesCompressedResp MPCResp )
            {
                int ind;
                int tmpID;
                int iTmp;
                int nPrices;
    
                lock( varsLock )
                {
    
    
                    lastUpdated = MPCResp.header.timestamp;
    
                    dBackPercent = 0;
                    dLayPercent = 0;
    
    
                    string mktPricesStr = MPCResp.marketPrices.Replace( "\\:", ";" );
                    string[] MPCArr = mktPricesStr.Split( ':' );
                    string[] mktDataArr = MPCArr[0].Split( '~' );
    
                    if( Convert.ToInt32( mktDataArr[0] ) != ID )
                    {
                        return false;
                    }
    
                    //mktDataArr[1];  // string Currency
    
                    status = mktDataArr[2];
    
                    delay = Convert.ToInt32( mktDataArr[3] );
    
                    nWinners = Convert.ToInt32( mktDataArr[4] );
    
                    info = mktDataArr[5];
    
    
                    //mktDataArr[6];  // bool Discount Allowed;
                    //mktDataArr[7];  // string Market Base Rate;
                    //mktDataArr[8];  // Long Refresh Time in MilliSeconds;
                    //TraceMsg("RefreshTime (" + mktDataArr[8] + ")");
    
    
                    removedRunners = mktDataArr[9];
    
                    //mktDataArr[10];  // string BSP Market = Y or N;
    
                    if( nRunners == 0 )
                    {
                        return ( false );
                    }
    
                    for( int r = 1; r < MPCArr.Count(); r++ )
                    {
                        string[] runnerArr = MPCArr[r].Split( '|' );
                        string[] rDataArr = runnerArr[0].Split( '~' );
    
                        tmpID = Convert.ToInt32( rDataArr[0] );
    
                        if( tmpID != this.RDATA[( r - 1 )].ID )
                        {
                            if( ( ind = getIndexFromID( tmpID ) ) < 0 )
                            {
                                UsrMsg( "ERROR: UpdateMarketPricesCompressed()->GetIndexFromID(" + tmpID.ToString() + ") FAILED: Could not find matching SELECTION ID" );
    
                                return false;
                            }
                        }
                        else
                        {
                            ind = ( r - 1 );
    
                        }
    
                        // rDataArr[1]   // int Order Index
                        // rDataArr[2]   // double Tot Ammount MAtched
    
                        RDATA[ind].lastPriceMatched = rDataArr[3].Length > 0 ? Convert.ToDouble( rDataArr[3] ) : 0;
    
                        // rDataArr[4]   // double Handicap
                        // rDataArr[5]   // double Reduction Factor
    
                        RDATA[ind].bVacant = rDataArr[6] == "true" ? true : false;
    
                        // rDataArr[7]   // double FAR SP Price
                        // rDataArr[8]   // double NEAR SP Price
    
                        //TraceMsg("rDataArr[9] = (" + rDataArr[9] + ")");
    
                        if( rDataArr[9] == "NaN" )
                        {
                            RDATA[ind].bsp = 0;
                        }
                        else
                        {
                            RDATA[ind].bsp = rDataArr[9].Length > 0 ? Convert.ToDouble( rDataArr[9] ) : 0;
                        }
    
                        for( int i = 0; i < 3; i++ )
                        {
                            RDATA[ind].BackPrice[i].dAmount = 0;
                            RDATA[ind].BackPrice[i].dOdds = 0;
                            RDATA[ind].LayPrice[i].dAmount = 0;
                            RDATA[ind].LayPrice[i].dOdds = 0;
                        }
    
                        string[] BackPricesArr = runnerArr[1].Split( '~' );
                        string[] LayPricesArr = runnerArr[2].Split( '~' );
    
                        // BackPricesArr[0] // double Odds
                        // BackPricesArr[1] // double Ammount Available
                        // BackPricesArr[2] // string 'L' = available to back, 'B' = available to Lay
                        // BackPricesArr[3] // int Depth, 1 = Best, 2,3
    
                        nPrices = ( BackPricesArr.Length - 1 ) / 4;
                        for( int i = 0; i < nPrices; i++ )
                        {
                            iTmp = i * 4;
    
                            RDATA[ind].BackPrice[i].dOdds = BackPricesArr[iTmp].Length > 0 ? Convert.ToDouble( BackPricesArr[iTmp] ) : 0;
                            RDATA[ind].BackPrice[i].dAmount = BackPricesArr[( iTmp + 1 )].Length > 0 ? Convert.ToDouble( BackPricesArr[( iTmp + 1 )] ) : 0;
    
    
                            if( i == 0 )
                            {
                                dBackPercent += ( 100 / RDATA[ind].BackPrice[i].dOdds );
                            }
                        }
    
    
                        nPrices = ( LayPricesArr.Length - 1 ) / 4;
                        for( int i = 0; i < nPrices; i++ )
                        {
                            iTmp = i * 4;
    
                            RDATA[ind].LayPrice[i].dOdds = LayPricesArr[iTmp].Length > 0 ? Convert.ToDouble( LayPricesArr[iTmp] ) : 0;
                            RDATA[ind].LayPrice[i].dAmount = LayPricesArr[( iTmp + 1 )].Length > 0 ? Convert.ToDouble( LayPricesArr[( iTmp + 1 )] ) : 0;
    
    
                            if( i == 0 )
                            {
                                dLayPercent += ( 100 / RDATA[ind].LayPrice[i].dOdds );
                            }
    
                        }
    
                    }
    
                }
    
                return true;
            }
            private int getIndexFromID( int tmpID )
            {
                for( int r = 0; r < this.RDATA.Count(); r++ )
                {
                    if( this.RDATA[r].ID == tmpID )
                    {
                        return ( r );
                    }
                }
    
                return ( -1 );
            }
    
            #endregion
    If you look at the UpdateMarketPricesCompressed() function ( above ) then you
    will notice some lines are commented out. These locate and describe the various
    data fields returned in the compressed string that we are not using ( but may use later )



    Part 6 Continues.... next post

    There is a 20k limit on each post so I have to split part 6 across posts
    Last edited by Escapee; 13-07-2012, 05:14 PM.

    Leave a comment:


  • Escapee
    replied
    I will try and add some more next week

    Leave a comment:


  • maxxim
    replied
    Escapee, thank you very much for the work done, and when it will be continued?

    Leave a comment:


  • Drifter
    replied
    Seconded!

    I particularly like the approach to threading the data refresh separately from the consumer elements, and ultimately the bet data. One can see how that could extend to the bet management side quite easily.

    Leave a comment:


  • AzureGulf
    replied
    Great stuff Escapee - many thanks - this will take me a while to properly understand, but I'm trying to keep up with each of your steps...

    I used an event handler to 'broadcast' messages from the API class but notice that these are not properly updated in my Form's statusStrip label - generally only the last API call message is displayed there, so hopefully your lesson above on 'crossing thread boundaries' will help.

    The structure and detail of your tutorial is excellent and I can appreciate the time and effort you've put into this - please keep going...

    Leave a comment:


  • Escapee
    replied
    Part 5

    Overview of the GUI design architecture
    Creating the Classes that hold the market data snapshot



    So far we have a GUI that just responds to user input. But to get this GUI
    to look and behave like betfairs standard web page we're going to have to do more
    than just respond to the user's clicking. We're going to have to go multi-threaded.

    The architecture I've used on several betfair programs has proved to be very flexible
    and very reusable and so I'm going to reuse it on this program as well.

    When you think about it, A market is comprised of some data about the market such as the name,
    Number of Winners etc. And then some data about each runner such as its name etc.

    At this point, we don't know 100% exactly what is going to need this data or how its going
    to displayed but we know we need it and we need the runner data refreshed every second.

    So what we do is create some class's and stuctures to hold this Market and Runner Data in
    Memory. With the idea that anything that might want to know about this data just has to access
    memory and doesn't have to individually bother with accessing betfair itself.

    The Picture Below ( Pic 5.1 ) gives a general overview of what the market memory consists of




    What this design does is seperate the data producer(s) from the data consumers.
    That way multiple controls, modules and threads can consume the same data at the same time
    and all the consumers can be sure they are all seeing the exact same data picture.

    The Picture Below ( Pic 5.2 ) gives a general overview of how the various modules interact
    with the Market Memory







    So now we're going to create the market memory class's and structures.
    There are various arguments as to where ( what module ) the memory class definitions should
    go. Without discussing all the pro's and cons I'm going to use and extend the BetfairAPI
    library module.

    Step 50
    Right click BetfairAPI in the solution explorer, select ADD->CLASS... and rename it to CRunnerData.
    then Select ADD to create the new class.

    Step 51
    Add the 'PriceStucture' to just above the RunnerData Class like this:
    Code:
    namespace BetfairAPI
    {
    [COLOR="Red"]    public struct PriceStruct
        {
            public double dAmount;
            public double dOdds;
        }[/COLOR]
        class CRunnerData
        {
        }
    }
    Step 52
    Add the contructor and variables within the CRunnerData class like this:
    ( don't forget to make the CRunnerData class 'public' )
    Code:
        [COLOR="Red"]public[/COLOR] class CRunnerData
        {
            public CRunnerData()
            {
                BackPrice = new PriceStruct[3];
                LayPrice = new PriceStruct[3];
    
                Name = "";
                ID = -1;
    
                AsianLineID = 0;
                Handicap = 0;
    
                bsp = 0;
                lastPriceMatched = 0;
    
                bVacant = false;
                GridRow = -1;
            }
    
            public PriceStruct[] BackPrice;
            public PriceStruct[] LayPrice;
    
            public string Name;
            public int ID;
    
            public int AsianLineID;
            public double Handicap;
    
            public double bsp;
            public double lastPriceMatched;
    
            public bool bVacant;
            public int GridRow;
    
        }
    Step 53
    Right click BetfairAPI in the solution explorer, select ADD->CLASS... and rename it to CMarketData.
    then Select ADD to create the new class.

    Make it 'public' like this:
    Code:
    namespace BetfairAPI
    {
        [COLOR="Red"]public [/COLOR]class CMarketData
        {
        }
    }
    Step 54
    Add the Variables within the class definition
    Code:
            #region Market Variables
    
            public volatile object varsLock = new object();
    
            private int _ID;
            public int ID
            {
                get { lock( varsLock ) { return _ID; } }
                set { lock( varsLock ) { _ID = value; } }
    
            }
            public int nRunners;
            public int nWinners;
            public string name;
            public string desc;
            public string info;
            public string removedRunners;
            public string menuPath;
            public string mktDisplayName;
    
            public int delay;
            public string status;
            public string prevStatus;
    
            public bool bspMarket;
            public int eventTypeID;
    
            public DateTime startTime;
            public DateTime lastUpdated;
    
    
            public CRunnerData[] RDATA;
    
    
            public double dBackPercent;
            public double dLayPercent;
    
            public bool bError;
            public string errorDescription;
    
    
            #endregion
    Step 55
    Add the code to output messages as and if required.

    Note: I add this code to virtually all my classes, so I'm going to start calling it
    the 'Standard UsrMsg Code'. Every time you create a new class which is going have anything
    more than just basic logic then just copy this and paste it in the new class.
    The TTraceMsg() function is particularly useful when developing as it takes the same sort
    of parameters as String.Format(), i.e. TTraceMsg("foo = {0}, bar = {1}, xxx = {2}",foo,bar,xxx);
    and writes it to the 'Output' window of visual studio.
    You will also need to add a "using System.Diagnostics;" line to the top of the file as well.
    Code:
            #region UsrMsg
    
            private UsrMsgDelegate UsrMsg;
    
            private void TraceMsg( string msg )
            {
                Debug.WriteLine( string.Format( "{0}$ TRACEMSG: CMarketData: {1}", DateTime.Now, msg ) );
            }
            public void TTraceMsg( string sFormat, params object[] objs )
            {
                Debug.WriteLine( string.Format( sFormat, objs ) );
            }
            public void MsgRedirect( UsrMsgDelegate msgHandler )
            {
                UsrMsg = msgHandler;
            }
    
            #endregion

    Step 56
    Below the UsrMsg Code add the Clear() function.
    Code:
            #region Functions to load and clear the Market and Runner data
    
            public void Clear()
            {
                ID = 0;
                nRunners = 0;
                nWinners = 0;
                name = "";
                desc = "";
                bspMarket = false;
                eventTypeID = 0;
                RDATA = null;
    
            }
    
            #endregion
    Step 57
    Just above the #region Market Variables at the top of the class add the constructor
    Code:
            #region Constructor / Destructor
    
            public CMarketData()
            {
                ID = -1;
    
                prevStatus = "Not Set";
    
                bError = false;
                errorDescription = "";
            }
    
            #endregion
    Save and compile to check all is OK.


    So Now we have the market data classes. We can and will flesh them out with functionality
    in parts 6 and 7 as we build the "Data Refresh" and "Market Display" modules.
    Thats when the GUI starts to look like the Betfair Web Page
    Last edited by Escapee; 20-06-2012, 07:52 PM.

    Leave a comment:


  • Escapee
    replied
    Parts 4, 5 & 6 are about building the background Framework stuff. This is
    the behind the scenes bit which allow all the front of house magic to appear
    effortless


    Part 4

    A Multipurpose Text Box. (simples)
    A Discussion on using Delegates and Modular Design (not so simples)



    Most GUI's will need to present and get text too and from the user in a multitude of formats.
    This GUI is no exception, so instead of writing a custom class for each different requirement,
    I use a single multipurpose "EBox" class and add functionality to it as required for each job it does.

    By the end of the project you'll have something you'll probably use on future GUI projects
    you build as text boxes are so ubiquitous and a multipurpose one is the way to go.

    Step 41
    Right click 'BetfairControls' in the solution explorer and select ADD->USER CONTROL
    rename it to "EBox", click ADD.

    Step 42
    Right Click 'EBox.cs' in Solution explorer and select VIEW CODE.
    change 'UserControl' to TextBox, like this:

    Code:
        public partial class EBox : [COLOR="Red"]TextBox[/COLOR]
        {
            public EBox()
            {
                InitializeComponent();
            }
        }

    Step 43

    As with all of our customised classes that were originally derived from 'UserControl'
    we need to comment out the auto generated line of code in the EBox.Designer.cs file
    which sets the AutoScaleMode.
    So Locate it and comment it out like this:

    Code:
            private void InitializeComponent()
            {
                components = new System.ComponentModel.Container();
                [COLOR="Red"]//[/COLOR]this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            }

    Step 44
    Now for some customisation.
    To Start with, I'm just going to add the functionality to get and set the Text,
    And also override the OnEnter & OnClick events so that when a user clicks/enters(tabs into)
    an EBox box the existing text is highlighted, then when he/she starts typing the old text will disappear.
    This saves a user faffing arround deleting the existing text before typing, as it does it automatically now.

    Add the following code to Ebox.cs

    Code:
        public partial class EBox : TextBox
        {
            public EBox()
            {
                InitializeComponent();
            }
    
            public delegate void dlgSetText( string txt );
            public void SetText( string txt )
            {
                if( InvokeRequired )
                {
                    BeginInvoke( new dlgSetText( SetText ), txt );
                    return;
                }
    
                this.Text = txt;
            }
    
            public delegate string dlgGetText();
            public string GetText()
            {
                if( InvokeRequired )
                {
                    return ( (string)Invoke( new dlgGetText( GetText ), null ) );
                }
    
                return ( this.Text );
            }
    
            protected override void OnEnter( EventArgs e )
            {
                base.OnEnter( e );
    
                if( this.ReadOnly == false )
                {
                    this.SelectAll();
                }
            }
            protected override void OnClick( EventArgs e )
            {
                base.OnClick( e );
    
                if( this.ReadOnly == false )
                {
                    this.SelectAll();
                }
            }
        }

    Save and Compile to check everything is OK.


    Part 4 INTERLUDE ( if you know about C# Delegates then Skip the Interlude )

    If you're a Novice C# programmer you may not have come accross or understand what delegates
    are for.

    I'm probably crap at explaining them and may even be a bit wrong but here goes........

    Q) What are delgates for ?

    A) Delegates are used to cross boundaries, either class, module and or thread boundaries.
    You can code right up to one side of the boundary with out caring or knowing what happens
    on the other side.
    Technically they are similar to function pointers in 'C'

    This might still sound a bit puzzling and WHY ?!
    so lets take a real example of what why and how.

    The CBetfairAPI class, at times, will want to put out error messages telling the user
    why something didn't happen. But the CBetfairAPI class doesn't know anything about what
    or where these messages might go.
    This GUI will show the messages on the screen, but future projects might want the messages
    in a logfile.

    As far as CBetfairAPI is concerned, what happens to its messages is outside its boundary.
    All it wants to do for each message is a single line of code and then just
    carry on with its own concerns.

    So thats exactly what we do, something like: UsrMsg("Some Important Messsage");
    and CBetfairAPI has coded upto its boundary.
    If, within CBetfairAPI, we define UsrMsg as a delegate, we can then hook up and code the stuff
    for processing and displaying or log-filing the message on the other side of the boundary at a later date.

    Q) So thats using delegates to take processing accross class and module boundaries,
    WHAT and WHY Thread Delegates ?

    A)
    One of the major uses of thread delegates is to update the screen.

    In C#, ( in simplified terms ) "Only the thread that owns (creates) the window can update the window"
    i.e. The GUI program may consist of many threads all doing different things in parallel. Anyone of these
    threads may want to put out an error message at any time, but they don't own ( didn't create ) the message
    box window so they have to pass thier messages to the thread that did create the message box window
    ( A.K.A the GUI thread ) and get the GUI thread to do the screen update.


    The whole delegate "thing" looks and is quite complicated at first, it can take a bit of time to sink in.

    The process of getting messages from a class such as CBetfairAPI to the screen requires the usage of a
    delgate to get accross the class boundary and the usage of another delgate to get across the thread boundary
    ( CBetfairAPI message may come from a thread which doesn't own the screen ).

    Now I've written it down like that it sounds ridiculously complicated in theory, but thread delegates
    in practice normally just involve a function checking if it needs to be executed on the GUI thread and
    then 'invoking' itself on the GUI thread if required.

    take the SetText() function of EBox as an example of thread Delegates

    Code:
            public delegate void dlgSetText( string txt );
            public void SetText( string txt )
            {
                if( InvokeRequired )
                {
                    BeginInvoke( new dlgSetText( SetText ), txt );
                    return;
                }
    
                this.Text = txt;
            }
    The first line: public delegate void dlgSetText( string txt );
    declares (defines) dlgSetText as a public delegate, that returns void and takes a string parameter.

    Then we have the code that implements SetText(). In an Ideal world we would just
    have this.Text = txt; and be done with it.
    But because of the "Only the thread that owns (creates) the window can update the window"
    constraint we need to check if the instance of this function is currently be executed by the GUI thread.
    So we check by if( InvokeRequired ) or long hand if( InvokeRequired == true )
    InvokeRequired is set to true by the .NET framework for all threads except the GUI thread.

    If InvokeRequired is set to true then we execute this line: BeginInvoke( new dlgSetText( SetText ), txt );
    and then return;

    BeginInvoke( new delegate( function ) , function parameters )

    will create a new instance of "function" using "function parameters" and then
    invoke(run) that function on the GUI thread.


    So trace the path of the SetText() function as though it was called from a random non-GUI
    thread.

    Enters SetText()
    Checks to see if its being executed by the GUI thread.
    NO.
    Creates a new instance of SetText() but with the same parameters.
    Invokes the new instance of the function on the GUI thread.
    returns back to its calling thread/function.

    Now what happens to the new instance of the function just invoked on the GUI thread.

    Checks to see if its being executed by the GUI thread.
    YES.
    this.Text = txt; // set the text i.e. show the message
    return

    in this scenario where we have used BeginInvoke, the final return is effectively
    the end of the new instance of SetText() and therefore the instance will be cleaned
    up by the garbage collector and the resources freed.

    If you compare SetText() with GetText() you will see a similar format except GetText()
    uses "Invoke" instead of "BeginInvoke"

    BeginInvoke = run it on GUI thread when you have time, I'm not hanging about for you to do it,
    I'm off to my next statement. The delegate gets put in a Queue for execution and will be handled
    in an orderly fashion by the GUI as appropiate.
    Invoke = run it on GUI thread NOW, I'm going to wait until the function returns.
    Effectively interupting what the GUI is doing now, and the thread that called it is 'blocking'
    waiting for a return.

    SetText() uses BeginInvoke as it doesn't require any returned data or return code, but GetText() uses
    Invoke as it requires the Text to be passed back through the chain.

    Be wary of using Invoke as it'll cause GUI freezes if two seperate calls clash, always use BeginInvoke
    if possible.

    Thread Delegates, Delegate the running of a function to another thread.

    Function ( class ) Delegates, Delegate the internal logic of a function to another
    (usually external) class.

    End Of INTERLUDE





    So after saving and compiling we now have our own customised Ebox available in the toolbox
    under "BetfairControls Components" when we go to the Form1.cs[Design] tab.




    Step 45
    Place an EBox on Form1 below the MenuTree and change its name to: "eMsgBox"

    Change the following in "eMsgBox" properties window:

    MultiLine = true
    ReadOnly = true
    BackColor = White

    Now that MultiLine has been set, we can enlarge eMsgBox on the Form Designer so that it fits
    ( with a margin ) on our form Like this: ( See Pic 4.2 )


    Now change the 'Anchor' property too: 'Bottom,Left,Right'
    This will cause the eMsgBox to resize itself automatically when the Form is resized by the user.


    Step 46
    Whilst we're here we might as well add the EBoxes where the user enters thier UserName and Password.

    Add an EBox to the Form next to the Login Button with the Properties:

    Name = 'eUsr'
    Text = 'UserName'

    And another EBox next to that, properties:
    Name = 'ePwd'
    Text = 'Password'
    UseSystemPassword = True

    After you've done all that then you should have something looking like this:



    ANOTHER INTERLUDE... ABOUT MODULAR DESIGN and some of the pros and cons.

    Modular design and build techniques have many benefits such as a high reusability factor,
    and a low or zero impact on other modules when one is changed or upgraded.

    But one drawback is getting modules to talk to each other seamlessly whilst still maintaining
    complete the independence of each module.
    If you think about the GUI task in hand, it has various modules: BetfairAPI, MenuTree etc and will
    have others which we have not created and or thought about yet, such as a Market Grid, Bets Tab Control
    etc etc.
    These various modules may or may not want to put out messages informing the user of success or failure.
    One of the ways of doing this is utilising delegates. ( Events and Event Handlers will come later in this
    tutorial )
    Specifically:
    'MyBetfair' is a submodule created in the Form1 constructor, and it will want to tell the
    user about various problems it might incur.
    'eMsgBox' is where we want 'MyBetfair' messages to go but MyBetfair doesn't know about the
    existence of eMsgBox which is also a submodule of Form1.
    And because we invested alot of time and effort in the CBetfairAPI class we will want to reuse it
    and we don't want to have to faff arround alot changing its messaging functions every time we reuse it.

    So what we do in CBetfairAPI (and all the other modules) is create the messaging function as a Delegate
    and in our main Form1 we reroute all these delegates to a function which puts out the messages in eMsgBox.

    Doing it this way, CBetfairAPI (and all the other modules) doesn't need to know anything about where its messages
    are going and hence can maintain independance and modularity. And you can change or upgrade eMsgBox without affecting
    any of the modules using it.

    Interlude Ends.


    Step 47
    In the CBetfairAPI.cs file add the following code ( Marked in RED )
    Code:
    namespace BetfairAPI
    {
        [COLOR="Red"]public delegate void UsrMsgDelegate( string msg );[/COLOR]
    
    
        public class CBetfairAPI
        {
    	..........
    	..........
    then copy this to below the Constructor

    Code:
            #region UsrMsg
    
            private UsrMsgDelegate UsrMsg;
            public void MsgRedirect( UsrMsgDelegate msgHandler )
            {
                UsrMsg = msgHandler;
            }
    
            private void TraceMsg( string msg )
            {
                Debug.WriteLine( string.Format( "{0}$ TRACEMSG: CBetfairAPI: {1}", DateTime.Now, msg ) );
            }
    
            #endregion


    then Change the "Debug.WriteLine" statements in the CheckResponse() function to "UsrMsg" like this:
    Code:
            private bool CheckResponse( string serviceName, string hdrErrCd, string respErrCd, string sessionToken )
            {
    
                if( ! string.IsNullOrEmpty( sessionToken ) )
                {
                    _sessionToken = sessionToken;
                    _globReqHdr.sessionToken = sessionToken;
                    _exchReqHdr.sessionToken = sessionToken;
                }
    
                if( hdrErrCd != "OK" )
                {
                    [COLOR="Red"]UsrMsg[/COLOR]( string.Format( "{0} - FAILED: Response.Header.ErrorCode = {1}", serviceName, hdrErrCd ) );
                    return ( false );
                }
                if( respErrCd != "OK" )
                {
                    [COLOR="Red"]UsrMsg[/COLOR]( string.Format( "{0} - FAILED: Response.ErrorCode = {1}", serviceName, respErrCd ) );
                    return ( false );
                }
    
                return ( true );
            }

    Step 48
    Now we go to editing the Form1.cs file, to reroute the messages BetfairAPI put out to eMsgBox.

    Add the UsrMsg() function to the Form1 Class below the constructor.

    Code:
            public delegate void dlgUsrMsg( string Msg );
            private void UsrMsg( string Msg )
            {
                if( InvokeRequired )
                {
                    BeginInvoke( new dlgUsrMsg( UsrMsg ), Msg );
                    return;
                }
    
                Debug.WriteLine( Msg );
    
                if( eMsgBox.Text.Length > 20000 )
                {
                    eMsgBox.Text.Remove( 0, 5000 );
                }
    
                eMsgBox.Text += Msg + "\r\n";
    
                eMsgBox.Select( eMsgBox.Text.Length, 0 );
                eMsgBox.ScrollToCaret();
    
            }
    Then, within the Form1 constructor add the line which reroutes the BetfairAPI messages via
    Form1.UsrMsg() to eMsgBox.

    Code:
            public Form1()
            {
                InitializeComponent();
    
                MyBetfair = new CBetfairAPI();
                [COLOR="Red"]MyBetfair.MsgRedirect( this.UsrMsg );[/COLOR]
    
                c_MenuTree.m_Betfair = MyBetfair;
    
                bLoggedOn = false;
            }
    Step 49
    Now edit the ButLogOn_Click() function to pick up the UserName and Password from the GUI fields
    we added earlier:
    Code:
            private void ButLogOn_Click( object sender, EventArgs e )
            {
                string uuu = "";
                string ppp = ""; 
    
    
                if( bLoggedOn == false )
                {
                    uuu = eUsr.GetText();
                    ppp = ePwd.GetText();
    
                    if( MyBetfair.Login( uuu, ppp ) == true )
                    {
                        eUsr.SetText( "UserName" );
                        ePwd.SetText( "Password" );
    
                        eUsr.Visible = false;
                        ePwd.Visible = false;
    
                        bLoggedOn = true;
    
                        c_MenuTree.Initialise();
    
                        ButLogOn.Text = "Log Out";
    
                        UsrMsg( "Logged On OK" );
    
                    }
                    else
                    {
                        UsrMsg( "Log on FAILED" );
                    }
                }
                else
                {
                    if( MyBetfair.LogOut() == true )
                    {
                        bLoggedOn = false;
    
                        ButLogOn.Text = "Log On";
    
                        UsrMsg( "Logged Out of Betfair" );
                    }
                    else
                    {
                        UsrMsg( "Log Out FAILED  !?" );
                    }
                    eUsr.Visible = true;
                    ePwd.Visible = true;
                }
            }


    Save, Compile and Run... You should start off with something like the GUI
    on the left ( pic 4.3 ) initially, which should change to look something like
    the gui on the right ( pic 4.3 ) after you've logged in correctly.

    Try logging in with an incorrect name, You should start seeing Error messages
    from CBetfairAPI appearing in eMsgBox.... that's Delegates for you.

    Last edited by Escapee; 20-06-2012, 07:33 PM.

    Leave a comment:

Working...
X