Blogs

This Blog

News

Favorite Posts

Archives

ctodx

Discussions, news and rants from the CTO of DevExpress, Julian M Bucknall

January 2010 - Posts

  • How to make a Flickr slideshow using DXperience ASP.NET

    One of the chapters I wrote for Professional DevExpress ASP.NET Controls was chapter 11 on asynchronous programming. In essence, I showed how to use the client-side functionality of the DevExpress ASP.NET controls, as well as using callbacks.

    One of the example programs I developed for that chapter was a slideshow application using an ASPxCallbackPanel, an ASPxRoundPanel (because I wanted it to look good), an ASPxImage, a few ASPxButtons, and an ASPxTimer. I glossed over the "where the photos come from" problem by just having an array of pre-initialized URLs. I always thought it was a bit of a daft excuse -- I cop to it being a cop-out, if you like -- but then again I was really describing the use of client-side JavaScript and AJAX and all that good stuff and didn't want to muddy it with a bunch of photo-handling code.

    Well, no more. Let's modify the app to get our photos from Flickr. The first thing we need is a great little open-source library called Flickr.NET so go download it from CodePlex. You only really need the binaries, but if you want to see how it's built, download the source as well. Add the FlickrNet assembly to the solution, a reference to it, and add the using FlickrNet; statement to the code-behind file.

    Now let's quickly build the visual part of the application. Drop an ASPxCallbackPanel onto the web project's main form. Drop an ASPxRoundPanel inside that, and then an ASPxImage inside of that. Create a <div> element underneath all that and drop four ASPxButtons  and an ASPxTimer inside that <div>. Set the various properties and names such that you get the following HTML code:

      <form id="form1" runat="server">
      <div>
        <dx:ASPxCallbackPanel ID="CallbackPanel" runat="server"  
          ClientInstanceName="callbackPanel" HideContentOnCallback="False" 
          oncallback="CallbackPanel_Callback" ShowLoadingPanel="False">
          <PanelCollection>
            <dx:PanelContent runat="server">
              <dx:ASPxRoundPanel ID="RoundPanel" runat="server">
                <PanelCollection>
                  <dx:PanelContent runat="server">
                    <dx:ASPxImage ID="ImageViewer" runat="server">
                    </dx:ASPxImage>
                  </dx:PanelContent>
                </PanelCollection>
              </dx:ASPxRoundPanel>
            </dx:PanelContent>
          </PanelCollection>
        </dx:ASPxCallbackPanel>
      </div>
      <div id="buttons">
        <div style="float:left;">
          <dx:ASPxButton ID="Prev" runat="server" Text="Previous" 
    ClientInstanceName="prevButton" EnableClientSideAPI="True" AutoPostBack="False"> <ClientSideEvents Click="function(s, e) {slideshowEngine.movePrev();}" /> </dx:ASPxButton> </div> <div style="float:left;"> <dx:ASPxButton ID="Play" runat="server" Text="Play"
    ClientInstanceName="playButton" EnableClientSideAPI="True" AutoPostBack="False"> <ClientSideEvents Click="function(s, e) {slideshowEngine.play();}" /> </dx:ASPxButton> </div> <div style="float:left;"> <dx:ASPxButton ID="Stop" runat="server" Text="Stop"
    ClientInstanceName="stopButton" EnableClientSideAPI="True" AutoPostBack="False"> <ClientSideEvents Click="function(s, e) {slideshowEngine.stop();}" /> </dx:ASPxButton> </div> <div style="float:left;"> <dx:ASPxButton ID="Next" runat="server" Text="Next"
    ClientInstanceName="nextButton" EnableClientSideAPI="True" AutoPostBack="False"> <ClientSideEvents Click="function(s, e) {slideshowEngine.buttonMoveNext();}" /> </dx:ASPxButton> </div> <dx:ASPxTimer ID="Timer" runat="server" ClientInstanceName="slideshowTimer" Interval="4000" Enabled="False"> <ClientSideEvents Init="function(s, e) {slideshowEngine.play();}" Tick="function(s, e) {slideshowEngine.tickMoveNext();}" /> </dx:ASPxTimer> </div> </form>

    In essence I named everything nicely (server-side and client-side) and set the client-side behavior of the buttons and timer to call various methods in a JavaScript object called slideshowEngine. I also created a callback method called CallbackPanel_Callback() in the code-behind C# file.

    I created a new JScript file called slideshow.js to the solution and placing it in the website folder. By dragging the file from the Solution Explorer to the default.aspx file and dropping it after the <form> element's end tag, Visual Studio created the <script> element for me.

      <script src="slideshow.js" type="text/javascript"></script>
    

    The JavaScript code looks like this:

    var slideshowEngine = function() {
      var timerOn = false;
    
      var setTimer = function(enable) {
        slideshowTimer.SetEnabled(enable);
        timerOn = true;
      };
    
      var moveNext = function() {
        if (!callbackPanel.InCallback()) {
          callbackPanel.PerformCallback("next");
        }
      };
    
      var movePrior = function() {
        setTimer(false);
        if (!callbackPanel.InCallback()) {
          callbackPanel.PerformCallback("prev");
        }
      };
    
      return {
        tickMoveNext: function() {
          if (timerOn) {
            moveNext();
          }
        },
        buttonMoveNext: function() {
          setTimer(false);
          moveNext();
        },
        movePrev: function() {
          movePrior();
        },
        play: function() {
          setTimer(true);
        },
        stop: function() {
          setTimer(false);
        }
      };
    } ();

    This uses what you might term an advanced style of JavaScript programming. In essence the slideshow object is defined as the result of running an anonymous function (although it looks to be declared as a function, look to the last line and you'll see that it is immediately executed -- you can see the function call parentheses). The reason for doing this is to strictly control what's public in the object: the function invocation will form a closure and return the anonymous object defined by the return statement. The only members that are visible in the created slideshow object will be tickMoveNext(), buttonMoveNext(), movePrev(), play(), and stop(). The rest of the code you can see will be in the function closure and invisible to the outside world.

    Anyway, moving on, let's see what we have to do in the code-behind file.

    In order to use anything in the Flickr API you have to have what's known as an apiKey. You can get these from Flickr, by logging in with your user ID, and going to http://www.flickr.com/services/api/keys. I created one specially for this demo, as you'll see below, but if you want to use this technique, you should create and use your own (you'd really hate it if I deleted mine on a whim).

    For this example, I just wanted to do a tagged search. This returns 100 photos at a time (or, more strictly, the details for 100 photos -- the actual images are available elsewhere), so it seemed to be a good idea to save the results of a search operation locally on the server in the Session object. I decided to encapsulate all the Flickr API work in a special class to make this easier, and to just expose a few methods that would enable the slideshow app to cycle through the images.

      public class FlickrResults {
        const string apiKey = "e28762539e823e30baedcee67e64cae4";
        private int loadedPage;
        private int number;
        private Flickr flickr = new Flickr(apiKey);
        private Photos photos;
    
        public FlickrResults() {
          loadedPage = -1;
        }
    
        public Photo GetPhoto() {
          int pageNum = number % 100;
    
          if (pageNum != loadedPage) {
            PhotoSearchOptions options = new PhotoSearchOptions();
            options.Tags = "yorkshire";
            options.PerPage = 100;
            options.Page = pageNum;
            options.SortOrder = PhotoSearchSortOrder.DateTakenDesc;
            photos = flickr.PhotosSearch(options);
            loadedPage = pageNum;
          }
    
          if (number > photos.TotalPhotos - 1)
            number = (int)photos.TotalPhotos - 1;
          return photos.PhotoCollection[number];
        }
    
        public void MoveNext() {
          number++;
        }
    
        public void MovePrior() {
          if (number > 0)
            number--;
        }
    
        public void Reset() {
          number = 0;
        }
      }

    Simple enough; the majority of the work is done in the GetPhoto() method. It works out the page number needed (at 100 photos per page) for the number of the photo requested. If the page of results hasn't been loaded yet, it'll make the call to Flickr to get the required page of photos. (Here I'm requesting photos tagged with "yorkshire", at 100 photos in a page, and ordering the results by the date taken in reverse order. Feel free to play around with your search options.) If the photo number happens to be greater than the total count of photos in the result set, I upper-bound the photo number. Finally return the current photo object from the result set.

    Next up is an easy little method to update the image and details in the browser:

        public void UpdateSlideShow() {
          var photo = results.GetPhoto();
          ImageViewer.ImageUrl = photo.MediumUrl;
          RoundPanel.HeaderText = photo.Title;
        }

    It gets the current photo object, sets the image URL to the medium-sized image and sets the round panel's header text to the photo's description. The results variable is created in the Page_Load() method:

        private const string ResultsName = "flickrResults";
        private FlickrResults results;
    
        protected void Page_Load(object sender, EventArgs e) {
          if (!IsCallback && !IsPostBack) {
            results = new FlickrResults();
            UpdateSlideShow();
            Session[ResultsName] = results;
          }
        }

    Of course, once created, the results object gets saved in the Session object, ready for being retrieved (and subsequently saved) during the callback:

        protected void CallbackPanel_Callback(object sender, CallbackEventArgsBase e) {
          results = (FlickrResults)Session[ResultsName];
          switch (e.Parameter) {
            case "prev":
              results.MovePrior();
              break;
            case "next":
              results.MoveNext();
              break;
            default:
              results.Reset();
              break;
          }
          UpdateSlideShow();
          Session[ResultsName] = results;
        }

    And that's pretty much it. Compiling and running the application produces a nice little web slideshow of photos tagged "yorkshire".

    DX-Flickr slideshow

    Happy programming!

  • Great review/intro to CodeRush's Test Runner, in French

    All this blogging in French on my part got Christian Ista (twitter: @christianista) to review the new Test Runner feature in CodeRush. In French. Let's put it like this: he's much better at it than I am Smile.

    It's a great synopsis of the new functionality and, as Christian points out, it's available in version 9.3.2 without any increase in price for all CodeRush users, providing of course that their subscription has not lapsed. It is not available in our free CodeRush Xpress. Christian also gives a well-deserved shout-out to the CodeRush/DXCore Community Plug-ins site on Google Code.

    Not only that, but Christian recorded a quick screencast showing the feature in action. You can read his blog post and see the video here.

    In unrelated news, I now know that "unit tests" is "tests unitaires" in French, "status bar" is "une barre d'états", "failed test" is either "test défectueux", "test en échec" or simply "test raté". (Magic, I really like that last one.) My technical French is getting better... Thanks Christian.

  • DevExpress Newsletter 20: Message from the CTO

    Reprinting my Message from the CTO from the twentieth newsletter so that you may comment on my thoughts.

    Assume your code will be public

    Back when I was a younger programmer than I am now, I remember writing cute but honest comments in my code. Things like "This is a hack, I'll fix it later", "This is to satisfy that stupid request that XYZ should happen", "One day I'll speed this up, but at least it works". And some of my identifier names could be a little risqué. All very well, since, of course, I was going to be the only person reading my code.

    Then it spread to my test data, making up charming first and last names, ridiculous addresses, lampooning famous people or just coworkers.

    Of course, you can guess what happened next. Someone high up caught a glimpse and didn't think it was funny. Oops. Later on, when the code I wrote was actually made public (it happens in the control vendor market, don't you know) a customer looked at one of my comments and started arguing about the situation it mocked. Double oops.

    So, if you take any advice from me in 2010 at least let it be this recommendation: write your code assuming that it will be public and scrutinized. Don't play funny games with it. Make sure your text -- be it error messages, test data, comments, whatever -- is squeaky clean. Don't end up on the Daily WTF with a red face.

    A fun one this time, but with a serious underlying point.

  • Encore quelques places à Paris

    Il y a encore quelques places pour notre événement de formation à Paris. Ceci est votre chance d'apprendre les contrôles ASP.NET de DevExpress et de s'amuser!

    Oliver Sturm et Mehul Harry présenteront la classe «Business Apps with DXperience in ASP.NET» le lundi et mardi Février 1 et 2 à Paris. Cette classe fournit un aperçu de la suite DXperience ASP.NET. Il vous emmène à travers les processus de création d'une application d'entreprise avec une interface externe et aussi interne, utilisant une combinaison typique des composants sur toute la gamme de produits DevExpress ASP.NET. Un niveau bien pratique des connaissances sera réalisé qui vous permet d'écrire des applications commerciales similaires.

    La classe sera présenté en Anglais, soit Américain. Vous pouvez vous inscrire pour la classe ici: European Training Roadshow.

    (Une note pour les véritables francophones: Soyez reconnaissants que nous ne parlons pas français Smile.)

  • Poor man's Bollinger bands

    Mehul's interview with Chris White (here) about EdgeRater was not only popular in the sense of seeing a successful stock market application written using DXperience, but also in the sense of the number of customers asking "when will XtraCharts support Bollinger bands" (EdgeRater uses Bollinger bands as the "edge" in making investment decisions). We are not currently planning to add Bollinger bands to XtraCharts this year, but until we do, here's a quick way to add them yourself.

    Aside: For more information on Bollinger bands, see wikipedia's article or see John Bollinger's website.

    In order to display Bollinger bands, we have some assumptions to make and some calculating to do. We assume that the data we're investigating are values over time. The archetypal example is of course stock prices, but it can be any measurement over time.

    The first band is a moving average of the data over some number of periods, the width of the periods being known as the window. This is known as the middle band. For example, if we're measuring fuel efficiency for our car in miles per gallon per minute (that is, our data points are values in mpg, and we calculate it every minute by measuring how far we've gone and how much fuel we used in that minute), we may decide that our moving average is calculated from the last 20 data points, or 20 minutes. The number of is the first "knob" in our Bollinger band calculator engine; the bigger the value, the more it "smoothes out" the variability in the original data. Call the width of the window, that is, the number of periods we use in calculating the moving average, N.

    The next two bands, known as the upper and lower bands, are calculated from the running standard deviations. In other words, over the same number of periods as we used for the moving average, we calculate the standard deviation each time. The upper band is calculated as some multiple (say 2) of the standard deviation over the average, and the lower band as the same multiple of the standard deviation under the average. The multiple is the second knob in the Bollinger band engine, although generally it's left at 2. Call this K. If the upper and lower bands are close together, it indicates low volatility in the original data; the wider apart they are, the greater the volatility. The significant points in the original data are those that lie above the upper band or below the lower band, and in a stock trading environment these are the points at which a trader might buy or sell the underlying stock.

    To chart the Bollinger bands, we therefore have to calculate the moving average and standard deviations of the original set of data. To do this efficiently, we make use of three summation variables. The first is merely the count of data points in our window. Usually this is N, but for the first N-1 windows it will take on the values 1 to N-1. Commonly, we just ignore the first N-1 moving averages and start our bands at the Nth period; this is what we'll do here. The second is the sum of the data values in the window, S, and the third is the sum of the squares of the data values in the window, Q. The moving average is then (S / N), and the standard deviation is sqrt(N.Q - S^2) / N.

    The nice things about using these running totals is that when we move the window along to the right by one period, we merely subtract the values at the point that slides out of the window on the left and add in the values for the point that slides in on the right. We do not have to calculate the entire total again from scratch every time. Also, it is this property that makes it ideal for calculating the bands for data that is constantly and frequently being added to.

    To illustrate how to use Bollinger bands using XtraCharts, I created this small WinForms application. Drop a chart control onto a new form and dock it to fill the form. Don't bother setting any properties using the Properties pane, we'll be doing it all via code.

    Change the form constructor to the following code. In it we shall load the data we're applying the Bollinger bands statistical analysis to (this will be a line series), and then calculate the bands themselves as area series.

        private int WindowSize = 20;
        private int SpreadSize = 2;
    
        public Form1() {
          InitializeComponent();
          LoadOriginalData();
          CalculateBands();
    
          var diagram = chartControl1.Diagram as XYDiagram;
          diagram.AxisX.DateTimeGridAlignment = DateTimeMeasurementUnit.Minute;
          diagram.AxisX.DateTimeMeasureUnit = DateTimeMeasurementUnit.Minute;
          diagram.AxisX.DateTimeOptions.Format = DateTimeFormat.ShortTime;
        }

    We also make sure that the X axis displays times. (The WindowSize constant is what we called N, SpreadSize is K.)

    The LoadOriginalData() method is pretty simple. I create a line series as a random set of points that has some random variability in the form of larger jumps, otherwise it just "jiggles" up and down.

        public double GetRandomValue(Random r, double oldValue) {
          double jump = 1.0;
          if (r.NextDouble() < 0.15)
            jump = (r.NextDouble() - 0.5) * (r.NextDouble() * 30.0);
          return (oldValue + jump) + ((r.NextDouble() - 0.6) * 5.0);
        }
    
        public void LoadOriginalData() {
          var series = new Series("OriginalData", ViewType.Line);
          series.ArgumentScaleType = ScaleType.DateTime;
          series.Label.Visible = false;
          var view = series.View as LineSeriesView;
          view.LineMarkerOptions.Visible = false;
    
          Random r = new Random();
          DateTime time = DateTime.Now;
          double value = 20.0;
    
          for (int i = 0; i < 200; i++) {
            time = time.AddMinutes(1);
            value = GetRandomValue(r, value);
            series.Points.Add(new SeriesPoint(time, value));
          }
    
          chartControl1.Series.Add(series);
        }

    I also make sure to turn off the point markers and the labels: there are 200 points here and with markers/labels visible it would look a complete mess.

    I then wrote a helper routine that would create a band as an area series, again turning off the labels and markers.

        private static Series GetBand(string name) {
          Series band = new Series(name, ViewType.Area);
          band.ArgumentScaleType = ScaleType.DateTime;
          band.Label.Visible = false;
          var view = band.View as AreaSeriesView;
          view.MarkerOptions.Visible = false;
          return band;
        }

    The CalculateBands() method is next.

        public void CalculateBands() {
          SeriesPointCollection originalData = chartControl1.Series[0].Points;
    
          Series middleBand = GetBand("MiddleBand");
          Series upperBand = GetBand("UpperBand");
          Series lowerBand = GetBand("LowerBand");
    
          double sum = 0.0;
          double sumSquares = 0.0;
    
          SeriesPoint point;
          for (int index = 0; index < originalData.Count; index++) {
            if (index < WindowSize) {
              point = (SeriesPoint)originalData[index];
              sum += point.Values[0];
              sumSquares += Math.Pow(point.Values[0], 2.0);
            }
            else {
              point = (SeriesPoint)originalData[index - WindowSize];
              sum -= point.Values[0];
              sumSquares -= Math.Pow(point.Values[0], 2.0);
    
              point = (SeriesPoint)originalData[index];
              sum += point.Values[0];
              sumSquares += Math.Pow(point.Values[0], 2.0);
    
              double mean = sum / WindowSize;
              middleBand.Points.Add(new SeriesPoint(point.Argument, mean));
              double stdDev = Math.Sqrt(WindowSize * sumSquares - sum * sum) / WindowSize;
              upperBand.Points.Add(new SeriesPoint(point.Argument, mean + stdDev * SpreadSize));
              lowerBand.Points.Add(new SeriesPoint(point.Argument, mean - stdDev * SpreadSize));
            }
          }
    
          chartControl1.Series.Add(upperBand);
          chartControl1.Series.Add(middleBand);
          chartControl1.Series.Add(lowerBand);
        }

    First of all we get a local copy of the original data series and create the three bands using my help routine. We initialize the running totals: sum and sumSquares.

    Now for the fun bit: making a pass through the original data to calculate the moving average and standard deviation. The first 20 (actually WindowSize) points are merely used to "seed" the running totals; these will not be reflected in the Bollinger bands themselves. Once we get past the first 20 points we can start calculating the moving average and the standard deviation. As described in the text above, we drop off the oldest data point from the running totals, and add in the new data point. We can then calculate the mean and add it to the middle band. After that we calculate the running standard deviation using the formula above, from which we can add the upper spread point to the upper band and the lower one to the lower band.

    Finally at the end of the method we add the new area series to the chart control, in order.

    Running the application gives us this (click for full-sized image):

    Bollinger Bands

    Now, of course, this is still only a rough version of what might be achievable (for a start, the colors could be chosen better: these are the default ones), but as a first approximation it's a good rendition of displaying Bollinger bands with XtraCharts.

  • DevExpress Newsletter 19: Message from the CTO

    Reprinting my Message from the CTO from the nineteenth newsletter so that you may comment on my thoughts.

    On commitment

    As I write this, Google have just announced their NexusOne phone, featuring the next version of the Android operating system. Alongside this announcement are news reports about a recent ChangeWave survey that said levels of user interest and satisfaction in Android are now approaching that of the iPhone. Windows Mobile is falling behind ever more rapidly, fourth behind Blackberry. Suppose you're a smartphone application developer: which phone OS do you target?

    To bring the question closer to home, since my readers are very likely to be PC or web application developers: which platform should you target? What if the choice you make or have made turns out to be the equivalent of Windows Mobile?

    At some point, after you've done your research, you have to commit and implement your plans. And you have to continue with them despite what might be happening in the world outside that might be invalidating your assumptions. If not, your project might turn into Duke Nukem Forever, where the game engine was changed multiple times before the product was abandoned unreleased.

    There are, I suppose, two strategies to minimize the risk: isolate the technology you're worried about behind an interface or framework and switch when necessary, or release early and often. The first might not be feasible anyway, and the second at least means you'll get some revenue and valuable feedback on functionality should a change in technology be required. Commit!

    In essence, my thoughts here can be summed up to "release early, release often", but I'm surprised how often the same possible hesitancy can occur in other situations. An obvious example is buying a new PC: you know very well that if you wait a month, the PC you want will be cheaper, more action-packed, sleeker, but of course after a month you know in a month's time it will be better still. So you vacillate.

    (Aside: to be brutally honest, the only times I've seen the "isolate behind an interface" option being used produced something that looked as if it had been designed by Architecture Astronauts.)

  • Roadmaps lack detail. Film at 11.

    I love Google Maps on the iPhone. If I find myself with 5 minutes to spare in a place I'm not familiar with, I can start the app, hit the GPS icon, and the screen shows me where I am. If I'm trying to see where I am in respect to other streets and the like, I use the map option. If I'm just wondering what it's like around here I can look at the satellite view to get an idea.

    The map is ideal for understanding the layout of the streets and understanding how to get from A to B. There is, however, no detail. It's essentially just a collection of vectors joined end to end and to each other, each vector having some attributes (start and end latitude and longitude being the primary ones, but type of road, speed limit, etc, are others). For detail, you have to switch to satellite view and then you have all the detail you want: houses, trees, open space, rivers, whatever.

    Our roadmaps are like the map view. You can see the general direction and the route we're planning to take, but there are little to no details. If you think about it for a little while, you'll understand why. We just haven't had the time to fully flesh out and design everything at the point where we discuss and decide on the roadmap. Instead all we have is a list of features, with some information about each feature, and from that starting point we discuss and make a decision about each. Yep, we could be making a decision based on insufficient or imperfect information, but it doesn't matter. If we waited until we did have sufficient or perfect information, we'd be publishing the roadmap at the end of the year in "satellite" view with flawless hindsight. It would of course be completely useless at that point, apart from as a "this year in review" type blog post.

    Of course, there will be errors in the roadmap. Some things won't get done. Some others we didn't mention, will. Others still will be done but in a different order. However the overall arch of the roadmap will be valid. For the 2010 roadmap that means more Silverlight and WPF; it means more work being done to make XAF/XPO the primary choice for business applications; it means polish type work for WinForms, some further investigations into ASP.NET MVC on the web side, and so on.

    But what it means for feature X (for numerous values of X) that's mentioned en passant in the roadmap, we don't fully know. We have some ideas, yes, otherwise it wouldn't be there, but how X will be designed and implemented is probably still being decided, and may even require something else to be done first. What it means for feature Y that's not mentioned at all: it's either too small to mention in a coarse-resolution roadmap, or it's probably not going to be done (but who knows).

    So, please don't take the roadmap to be any more than it already is. There is no subtext to it. Although I mostly make my living from writing these days, I didn't have the time to add Dan Brown style hints and clues to it: What you see is what you get. It's a map, not a satellite or street view.

    (Aside: for customers who don't know the idiom, here's the explanation for "Film at 11".);

  • Build 48 of the VCL subscription published

    In case you missed the announcement email that was sent out, Build 48 of the VCL subscription has now been published.

    The big new feature of this build is ExpressPrinting System v4. We've been blogging about the new enhancements in this release since we published it in beta (here and here, for example), and it's now fully released. The new features include:

    • PDF Export. Reports can now be exported to PDF. A full set of options are available, including compression, font embedding, security.
    • Print Preview Dialog style options. Choose between standard, advanced, ribbon styles. The ribbon style is new.
    • Skins and Look-And-Feel styles for built-in dialogs. The built-in dialogs now support your application's skin.
    • Many other miscellaneous features.

    Apart from ExpressPrinting System v4, there are many changes, fixes, and enhancements to other VCL controls, including the grid, the bars suite, the skinning library, the tree list, the layout control, and so on.

    For the full list of changes please review the What's New here. Note that there are also some breaking changes. Please review that list here.

  • DevExpress Roadmap 2010

    The roadmap for Developer Express in 2010 has just been published. You can find it here.

    I recommend that all customers read it, not only because of the news about features and enhancements across our entire product line, but also especially as it contains vital new information about how we plan to publish new versions in 2010.

LIVE CHAT

Chat is one of the many ways you can contact members of the DevExpress Team.
We are available Monday-Friday between 7:30am and 4:30pm Pacific Time.

If you need additional product information, write to us at info@devexpress.com or call us at +1 (818) 844-3383

FOLLOW US

DevExpress engineers feature-complete Presentation Controls, IDE Productivity Tools, Business Application Frameworks, and Reporting Systems for Visual Studio, along with high-performance HTML JS Mobile Frameworks for developers targeting iOS, Android and Windows Phone. Whether using WPF, Silverlight, ASP.NET, WinForms, HTML5 or Windows 8, DevExpress tools help you build and deliver your best in the shortest time possible.

Copyright © 1998-2014 Developer Express Inc.
All trademarks or registered trademarks are property of their respective owners