C# a Step by Step Beginners Tutorial

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • AzureGulf
    Junior Member
    • May 2012
    • 5

    #16
    Compression Flag and sorting MenuTree

    Escapee - I think you're correct in setting DeCompression to true in order to enable compression, from what I've seen in the VB2008 thread (by Mumbles0) in this forum...

    By the way, when you're building your MenuTree, you might like to sort the Events/Markets using code a bit like this:

    Code:
                                    var sortedEvents = from events in response.eventItems
                                                 orderby events.menuLevel, events.eventName
                                                 select events;
    
                                    foreach (BFEvent bfEvent in sortedEvents)
                                    {
                                      ... add to menu tree as per your example...
                                    }
    Needless to say, the same can be done for the market items in the next block of code...you might like to check the correct sort order, so that markets don't become disassociated from their relevant events!

    Looking forward to your next post

    Comment

    • Drifter
      Junior Member
      • Mar 2009
      • 30

      #17
      Compression is used because the free API allows 60 calls per minute to the compressed functions, but only 10 to the raw versions.

      The 5* thing may have been me - I didn't realise it was global. I have such power...

      Comment

      • AlgoTrader
        Junior Member
        • Mar 2012
        • 243

        #18
        Originally posted by Drifter View Post
        Compression is used because the free API allows 60 calls per minute to the compressed functions, but only 10 to the raw versions.
        Compression is usually mentioned related to gzip HTTP compression. calls like getAllMarkets can contain hundreds of kilobytes and be quite slow to receive data. GZIP compressiom reduce network traffic in 10 times, it will be just dozens of kilobytes to be transmitted. The latency drops drastically.

        Another thing that drops latency is persistent connections. It takes 15ms to call getAlllMarketPricesCompressed when I use already established connection and 90ms when there is no ready connection.

        Ignoring those issues makes the bot an underdog
        Betfair Bots Made Easy

        Comment

        • Escapee
          Junior Member
          • Feb 2009
          • 51

          #19
          AzureGulf:
          By the way, when you're building your MenuTree, you might like to sort the Events/Markets using code a bit like this:
          Thats a good way of doing it. I kind of forget that C# comes with LINQ built in and don't often use it.
          In the past I have just written custom callback functions for MenuTree sorting but thinking about it your method would do 80% of the time.

          Comment

          • Escapee
            Junior Member
            • Feb 2009
            • 51

            #20
            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.

            Comment

            • Escapee
              Junior Member
              • Feb 2009
              • 51

              #21
              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.

              Comment

              • AzureGulf
                Junior Member
                • May 2012
                • 5

                #22
                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...

                Comment

                • Drifter
                  Junior Member
                  • Mar 2009
                  • 30

                  #23
                  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.

                  Comment

                  • maxxim
                    Junior Member
                    • Jan 2010
                    • 7

                    #24
                    Escapee, thank you very much for the work done, and when it will be continued?

                    Comment

                    • Escapee
                      Junior Member
                      • Feb 2009
                      • 51

                      #25
                      I will try and add some more next week

                      Comment

                      • Escapee
                        Junior Member
                        • Feb 2009
                        • 51

                        #26
                        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.

                        Comment

                        • Escapee
                          Junior Member
                          • Feb 2009
                          • 51

                          #27
                          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.

                          Comment

                          • twisterpb
                            Junior Member
                            • Jul 2012
                            • 1

                            #28
                            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

                            Comment

                            • Drifter
                              Junior Member
                              • Mar 2009
                              • 30

                              #29
                              Likewise

                              All going well over here too.



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

                              Comment

                              • AzureGulf
                                Junior Member
                                • May 2012
                                • 5

                                #30
                                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

                                Comment

                                Working...
                                X