in
Forums
Blogs
DevExpress.com
Client Center
Support Center
DevExpress Channel

This Blog

Syndication

News

ctodx

Discussions, news and rants from the CTO of Developer Express, Julian M Bucknall
  • Curl up in front of a roaring fire with our EULA

    OK, the headline is said partly in jest, but the intent is serious. You see, we had a weird support case over the weekend where tempers were starting to rise, until Alex in support worked out what was going on.

    The customer was writing a small test program with ASPxScheduler. Worked fine on his development machine — yes, it's the perennial programmer's excuse Smile — but when he copied the test app over to the production server with all required DevExpress assemblies and ran it, blam, the Yellow Screen of Death. The error was frankly bizarre: "Make sure that the class defined in this code file matches the "inherits" attribute, and that it extends the correct base class." Nice, but what the … does it mean?

    Of course, the support team couldn't replicate the problem either (more "works on my machine" in other words). We thought it was one thing and then another, nothing seemed to make sense, and every back and forth would dial up the frustration and the tense atmosphere towards the magical 11. Even Mehul was dragged into the fray, and he's in Paris.

    Finally this morning, Alex noticed something in a screenshot: the customer was copying the *.design.dlls to the production website as well. The customer considered them part of the "required DevExpress dlls" and had unknowingly copied them over along with the run-time dlls. Our support guys, knowing the language in the EULA, had naturally assumed that this wasn't the case and so were trying more and more outlandish scenarios trying to replicate the customer's problem. Removing the design-time-only dlls solved the issue.

    The moral of the story is this: the EULA not only defines your rights and ours and is admittedly quite boring to read, but it does also define a long list of dlls that you can — are allowed to — deploy along with your application. None of the *.design.dlls are deployable.

    So, take a moment to read our EULA. You may save yourself some support time in the future…

  • 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.

  • DevExpress Newsletter 17: Message from the CTO

    Dual-posting Julian's Message from the CTO from DX Press, the DevExpress Newsletter, issue 17.

    Standards

    This particular rental car I’m driving this trip is … annoying. It’s nippy enough and can hold me and my luggage with room to spare, but every time I indicate to overtake or to change direction, I turn on the windshield wipers. You see, my own car has the indicators on the left stalk and this car has them on the right. Heaven knows what the other drivers must think as my wipers frantically swish across the windshield as I turn into a side street.

    It’s all a matter of standards. Manufacturers have agreed on the placement of the pedals in a car, but the rest is a mishmash. We have standards too in software: not only in the user interface, but also in “hidden” areas like XML, communication protocols, file formats, and the like. When we write software, it’s as if half of our design decisions have been made for us already. And note I am not knocking this situation, far from it. I still remember that Esc brought up the menu in Word for DOS.

    But sometimes, we implement something that is brand new. It behooves us, in that case, to try and make decisions that could frame some new standards so that other people can follow them (and thereby ensuring their “standardness”). At DevExpress, we encounter this type of scenario relatively often: every new control design that hasn’t been implemented elsewhere will need us to decide on mouse usage, keyboard shortcuts, text placement, and so forth, although we can leverage other standards for things like icons and similar.

    So, embrace standards, for without them, your work would be that much harder to design and complete.

  • Summit and Roadmap news

    RoadmapWe've reached lunchtime on the last day of our DevExpress Summit. The Summit is the occasion where, every year, around the New Year, the management, team leads, and evangelists all meet together, listen and watch each other's presentations about where we are, and where we'd like to be, and discuss the proposed features and enhancements for the upcoming year. Of course, this time around, it's all about the Roadmap for 2010.

    When we published the 2009 Roadmap, we decided to experiment and release it as a series of blog posts as and when we decided on a particular platform or product suite. Although this meant that customers got the news hot off the presses before the ink had even dried, it did have the consequence that a couple of months into the year, finding the "Roadmap" turned into a longwinded exercise in searching the Community site. We received a lot of feedback throughout 2009 that this was, shall we say, not the most stellar decision we ever made. Well, you've got to experiment to improve things and you've got to move on if the experiment fails.

    This year then, we are reverting to the "old" style of publishing a roadmap: posting a single web page on our main site. That's why we've been mostly silent through the whole week — at least compared with last January.

    My job Monday is to amalgamate all my notes and all the Powerpoint slidedecks into a coherent narrative that describes the whole DevExpress Roadmap for 2010. I'll be circulating it to everyone here so that they can check their sections (heaven forbid I add something on the sly Wink), and so it should be published Wednesday or Thursday. Of course, I'll announce it here, and it will be prominently displayed on the home page.

  • Naming anonymous types

    I've been futzing around recently trying to get a grip on LINQ to XML for an internal project. Yeah, I know, everyone else has moved on having solved that particular problem a couple of years ago, but for some reason, although I understood the concepts and the infrastructure behind LINQ, I'd never really coded anything. Until this afternoon, that is.

    Not one to take little nibbles, I decided to attack iTunes Music Library.xml.

    If you've never taken a peek at this file, here's the header plus the top two tracks from mine:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
      <dict>
        <key>Major Version</key><integer>1</integer>
        <key>Minor Version</key><integer>1</integer>
        <key>Application Version</key><string>9.0.2</string>
        <key>Features</key><integer>5</integer>
        <key>Show Content Ratings</key><true/>
        <key>Music Folder</key><string>file://localhost/O:/My%20Music/iTunes/iTunes%20Music/</string>
        <key>Library Persistent ID</key><string>277AA4870A436D01</string>
        <key>Tracks</key>
        <dict>
          <key>1656</key>
          <dict>
            <key>Track ID</key><integer>1656</integer>
            <key>Name</key><string>Voices</string>
            <key>Artist</key><string>Vangelis</string>
            <key>Album Artist</key><string>Vangelis</string>
            <key>Composer</key><string>Vangelis</string>key>Album</key><string>Voices</string> 
            <key>Genre</key><string>Electronica</string>
            <key>Kind</key><string>MPEG audio file</string>
            <key>Size</key><integer>5064895</integer>
            <key>Total Time</key><integer>421381</integer>
            <key>Track Number</key><integer>1</integer>
            <key>Year</key><integer>1995</integer>
            <key>Date Modified</key><date>2008-02-09T21:05:38Z</date>
            <key>Date Added</key><date>2005-05-06T23:21:31Z</date>
            <key>Bit Rate</key><integer>96</integer>
            <key>Sample Rate</key><integer>44100</integer>
            <key>Play Count</key><integer>10</integer> 
            <key>Play Date</key><integer>3327338137</integer>
            <key>Play Date UTC</key><date>2009-06-09T03:35:37Z</date>
            <key>Artwork Count</key><integer>1</integer>
            <key>Persistent ID</key><string>277AA4870A436D0E</string>
            <key>Track Type</key><string>File</string>
            <key>Location</key><string>file://localhost/M:/My%20Music/Vangelis/Voices/01%20-%20Voices.mp3</string>
            <key>File Folder Count</key><integer>-1</integer>
            <key>Library Folder Count</key><integer>-1</integer>
          </dict>
          <key>1658</key>
          <dict>
            <key>Track ID</key><integer>1658</integer>
            <key>Name</key><string>Echoes</string>
            <key>Artist</key><string>Vangelis</string>
            <key>Album Artist</key><string>Vangelis</string>
            <key>Composer</key><string>Vangelis</string>
            <key>Album</key><string>Voices</string>
            <key>Genre</key><string>Electronica</string>
            <key>Kind</key><string>MPEG audio file</string>
            <key>Size</key><integer>6067997</integer>
            <key>Total Time</key><integer>504973</integer>
            <key>Track Number</key><integer>2</integer>
            <key>Year</key><integer>1995</integer>
            <key>Date Modified</key><date>2008-02-09T21:05:38Z</date>
            <key>Date Added</key><date>2005-05-06T23:21:31Z</date>
            <key>Bit Rate</key><integer>96</integer>
            <key>Sample Rate</key><integer>44100</integer>
            <key>Play Count</key><integer>9</integer>
            <key>Play Date</key><integer>3330797581</integer>
            <key>Play Date UTC</key><date>2009-07-19T04:33:01Z</date>
            <key>Artwork Count</key><integer>1</integer>
            <key>Persistent ID</key><string>277AA4870A436D0F</string>
            <key>Track Type</key><string>File</string>
            <key>Location</key><string>file://localhost/M:/My%20Music/Vangelis/Voices/02%20-%20Echoes.mp3</string>
            <key>File Folder Count</key><integer>-1</integer>
            <key>Library Folder Count</key><integer>-1</integer>
          </dict>
    

    Yep, it's a mess. The design is not brilliant: I was expecting to see major elements like "song", and inner elements called "Album" and "Artist" and the like. But no: it's a dictionary with every entry in the dictionary defined as a key-value pair or as another dictionary entry. Bleugh.

    I almost tossed the idea, but then decided to do a quick search to see if anyone had figured out what to do. I came across this article by Joshua Allen in his Better Living Through Software blog. In it he described a method using LINQ that transformed the iTunes file into what I might call more normal-looking XML. So I took that idea and ran with it. Essentially I wanted a method that would return a LINQ result set containing cleaned up data, with POCOs (plain old C# objects) and no XML. (In essence, I was going to feed this directly into one of our grids through its DataSource property.)

    First I loaded the XML file into an XDocument:

    XDocument iTunes = XDocument.Load(@"O:\My Music\iTunes\iTunes Music Library.xml");

    Then I applied Joshua's LINQ expression to convert the iTunes XML into something more palatable:

    var rawtracks = from track in iTunes.Descendants("plist").Descendants("dict").Descendants("dict").Descendants("dict")
                    select new XElement("track",
                        from key in track.Descendants("key")
                        select new XElement(
                           ((string)key).Replace(" ", ""),
                           (string)(XElement)key.NextNode)
                        );

    Remember, until I enumerate the data from the result set, the only thing that happens with this code is that an expression tree is built. No data has been harmed yet! The result set from this is a list of XElements each of whose names is the contents of the <key> element, and whose content is the node that follows the <key> node.

    Next, I want to select the actual tracks from this result set (by "actual" I mean that there is an MP3 file behind the track: for some reason the original XML file has a lot of empty entries) and in doing so I want to create an enumerable list of POCOs. First a couple of helper methods:

        private static string XElementToString(XElement e, string defaultValue) {
          if (e == null) 
            return defaultValue;
          return e.Value;
        }
    
        private static TimeSpan XElementToTimeSpan(XElement e, TimeSpan defaultValue) {
          if (e == null)
            return defaultValue;
          return new TimeSpan(Int64.Parse(e.Value) * 10000);
        }

    And then the LINQ statement:

    var tracks = from track in rawtracks
                 where track.Element("Location") != null
                 select new {
                   Artist = XElementToString(track.Element("Artist"), string.Empty),
                   Album = XElementToString(track.Element("Album"), string.Empty),
                   Name = XElementToString(track.Element("Name"), string.Empty),
                   Time = XElementToTimeSpan(track.Element("TotalTime"), defaultTime)
                 };

    OK, a quick explanation is in order. For each track in the result set I'm creating a new anonymous object with four properties: Artist, Album, Name, and Time. The tracks result set is what I need to attach to the DataSource property of my grid. (Again, executing this last statement does not do anything with the data: that only happens when the grid enumerates the result set.)

    Now, this works very well for my original requirement, but there is a big problem for any other code. If I wrap this up in a method to return the tracks object, what type does the method return? It's an IEnumerable<T>, but what is T? The only thing it can return is object, which is not exactly informative at code time.

    Enter the Name Anonymous Type refactoring from Refactor! Pro. Place the caret on the new keyword (it's easier to hit than the opening brace, which is another activation site), press the refactor key, and select Name Anonymous Type. After renaming the default name, you will get this for the LINQ statement:

    var tracks = from track in rawtracks
                 where track.Element("Location") != null
                 select new Track(
                   XElementToString(track.Element("Artist"), string.Empty), 
                   XElementToString(track.Element("Album"), string.Empty), 
                   XElementToString(track.Element("Name"), string.Empty), 
                   XElementToTimeSpan(track.Element("TotalTime"), defaultTime));

    It's created a new type called Track and news up another object of this type for every track found in the result set. And what does Track look like?

        [DebuggerDisplay("\\{ Artist = {Artist}, Album = {Album}, Name = {Name}, Time = {Time} \\}")]
        private sealed class Track : IEquatable<Track> {
          private readonly string artist;
          private readonly string album;
          private readonly string name;
          private readonly TimeSpan time;
    
          public Track(string artist, string album, string name, TimeSpan time) {
            this.artist = artist;
            this.album = album;
            this.name = name;
            this.time = time;
          }
    
          public override bool Equals(object obj) {
            if (obj is Track)
              return Equals((Track)obj);
            return false;
          }
    
          public bool Equals(Track obj) {
            if (obj == null)
              return false;
            if (!EqualityComparer<string>.Default.Equals(artist, obj.artist))
              return false;
            if (!EqualityComparer<string>.Default.Equals(album, obj.album))
              return false;
            if (!EqualityComparer<string>.Default.Equals(name, obj.name))
              return false;
            if (!EqualityComparer<TimeSpan>.Default.Equals(time, obj.time))
              return false;
            return true;
          }
    
          public override int GetHashCode() {
            int hash = 0;
            hash ^= EqualityComparer<string>.Default.GetHashCode(artist);
            hash ^= EqualityComparer<string>.Default.GetHashCode(album);
            hash ^= EqualityComparer<string>.Default.GetHashCode(name);
            hash ^= EqualityComparer<TimeSpan>.Default.GetHashCode(time);
            return hash;
          }
    
          public override string ToString() {
            return String.Format("{{ Artist = {0}, Album = {1}, Name = {2}, Time = {3} }}", artist, album, name, time);
          }
    
          public string Artist {
            get { return artist; }
          }
          public string Album {
            get { return album; }
          }
          public string Name {
            get { return name; }
          }
          public TimeSpan Time {
            get { return time; }
          }
        }

    This class is not just a bag o' properties either. Just look at what the refactoring decorates it with:

    • A DebuggerDisplay attribute for use when displaying objects of this type with the debugger. Nice.
    • The IEquatable<T> interface. Objects of this class can be compared equal with the semantics and meaning you expect.
    • Readonly fields. The class is readonly from the start.
    • The Equals() methods to fulfill the IEquatable<T> contract
    • A meaningful GetHashCode() implementation that uses all the class' properties.
    • A good ToString() method that shows all the property values.

    (I'll note that this declaration is pretty much identical to what the C# compiler builds for you when it compiles an anonymous type.)

    Of course, now the code for the class has been created, I can alter it in any way I see fit. For example, I can change properties to be writable, I can reduce the influence of the GetHashCode() method by removing some of the field values, and so on.

    But most of all I can change the return type of my putative method to IEnumerable<Track> and move on. Just brilliant.

  • DevExpress Newsletter 16: Message from the CTO

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

    Virtualizing your experiments

    At PDC this year, I wanted to show off some of the new things we have in v2009.3, but at the time I was setting up my laptop there was only a beta available. Rush-released that very morning, in fact, so I wasn't hopeful it would last beyond the day. No matter, no problem, I just installed it in a virtual machine.

    Virtual machines have changed the lives of developers everywhere. Need a pristine PC? Just clone the pristine virtual machine you have ready for that purpose (mine has Windows 7 plus Visual Studio 2008 freshly installed, all updated) and boot it. Need to test some alpha build and you're worried about it affecting your usual environment? Boot up another clone. Once you're done with your experiments, you can just throw the virtual machine away.

    Back in the day, I used to have a spare physical PC and a copy of Norton Ghost. I'd create a pristine OS install, Ghost it, and then play merry havoc on the PC with dodgy software. Once I'd done, I'd reimage the pristine install from the Ghost image. But, compared with a VM, it took a long time to get to the "good" state.

    Since there are several hypervisors around, you have no excuse. I use VMWare Workstation, but there's also Virtual PC and VirtualBox, both free, and on the Mac there's Parallels and VMWare Fusion. Once you have a hypervisor, all that's required is plenty of disk space to hold the virtual disks (these will be 20GB or so in size).

    So protect yourself with a VM and experiment with abandon.

    Actually, virtualization is slowly being accepted by the non-programming masses too. For example, with the top-of-the-range Windows 7 package, you can download a specialized virtualization app that only works with Windows XP. I wonder how soon before all we do is run software in virtual machines, and our host OS is just a hypervisor.

  • Sneak Peek: Refreshing UI in XtraReports v2009.3

    A few weeks ago I published a blog post about the new report designer in our reporting product. This is due to be released very soon and, to be honest, for such a great feature, my post left a little to be desired in terms of details. Alex, who writes for the XtraCharts and XtraReports teams, thought it would be a good idea for me to interview members of the development team and get them to reveal more about the new XtraReports.

    KonstantinK For this third and final article, he hooked me up with Konstantin Kosukhin, a.k.a. Konstantin K in the forums. Konstantin rocked my world with the new look-and-feel for the designer: he and I are both fans of Mark Miller's series of posts and talks on The Science of Great UI. This time I'll show lots of before and after pictures: he's done a cracking good job in refreshing the UI. (Yes, I am most definitely punning in the title to this post.) Since this is the last article in this small series, I'd also just like to say thanks to Alex (who likes to be known as Alan in the forums) for setting all this up and the work he did in making sure the interviews came off well.

    (Note: Although all screenshots in this article are from the End-User Report Designer, the Visual Studio report designer has all the same enhancements. The only exception is the toolbox and the property grid, which are native VS elements, only available at VS design time).

    JMB: What gave you the idea to revamp the designer UI?

    Konstantin: Easy. Just look at the designer in v2009.2 and earlier:

    Designer in 9.2 

    It's functional in that you can design reports with it, but just look at that visual noise. It looks heavy. It's hard to tell what visual objects are part of the report and what are part of the designer. It violates a lot of the guidance that Mark Miller laid down in his Science of Great UI blog posts.

    So, we decided to make the designer's appearance more lightweight and not beat you over the head with it. So we redid some design work and produced this:

    Designer in 9.3

    I'm sure everyone will agree it looks more restful, and it has not lost any of the functionality.

    JMB: Very nice, indeed. I like the attention you paid to reducing contrast and your better use of color. The visual noise is way down. So, take us through the changes to get to this new version.

    Konstantin: First up for the chop were the grid lines. Grid lines are shown on the report designer surface to make it easier to move and resize report elements. Previously, we used dotted lines but there was no difference between the major and minor lines. All grid lines were the same. Also, another problem was that the grid line increment was measured in pixels, which was most inconvenient for designing reports that were measured in inches or centimeters.

    GridLines 9.2

    We threw away the dotted lines and replaces them with thin low-contrast silver grid lines, with the major subdivision lines being slightly darker than the minor ones. The grid line increment is now measured in the units for the current report.

    GridLines 9.3

    Of course, the new snap lines feature blends in with this new grid very nicely.

    JMB: Totally agree. The dotted grid was very 90s and this new version is much better. More modern. What else?

    Konstantin: Next on the list was what a selected control looked like. Previously, when a report control was selected, it was drawn with a rather noisy selection rectangle.

    Selection_92

    We wiped that design to replace it with a very lightweight selection box, with judicious use of some slight bluish color.

    Selection 9.3

    Already you can see that the high-contrast color of the control contents stands out from the infrastructure of the designer. Look back at the previous image: the dots were merging with the text to produce a very jagged look.

    JMB: I'm liking this new designer more and more. Looking back at the overall picture I see you also altered the report band strips.

    Konstantin: Exactly. In the previous version, the strips that indicated the different report bands were brightly colored and used a saturated color as well. They really dragged your focus away from the report controls, arguably the most important elements on the designer surface, to something that was in essence a set of dividing lines.

    BandStrips 9.2

    I mean talk about visual noise: all you can see on the designer surface is band strips. And just look at the jaggies because of the dotted grid lines. For v2009.3 we turned the contrast and color of the strips way down. Just enough to indicate a division, and the name of that division:

    BandStrips 9.3

    JMB: I think you just saved me from a headache.

    Konstantin: The final change we made to the designer surface was how we treated the margins of the report. We were way too literal about margins in v2009.2 and earlier:

    ReportMargins_92

    I mean, after all, the designer surface is supposed to represent a sheet of paper. To emphasize that similarity, we decided to draw indents from the left and top rulers, show the left report margin on the screen, along with making the top and bottom margins always visible on the report designer surface (previously, only the right report margin was visible). This makes it way easier for a user to change the report margins when designing a report without having to resort to using the Property Grid. Just drag and drop.

    ReportMargins 9.3

    JMB: I like it. I completely agree that being able to drag the margins to resize them is a much better user experience than having to search for and then select the right property in the Property Grid. I also notice you changed the toolbox.

    Konstantin: Yes, this was the final major change we made. We decided that the dock panel we had been using as the toolbox was too heavy for regular use, and felt a simpler bar would be better.

    On the fourth day of DevExpress Christmas, Julian gave me slick new 3D charts for XtraCharts First, it allows us to provide more space for the designer surface, that is, the user's report. Second, it means that someone who writes reports often is not distracted by the icon plus text of the old dock panel but can still locate controls very quickly. For the user who is just starting out with the designer, the names of the toolbox items are displayed in tooltips, and in the previous version (v2009.2) we'd already made the toolbox icons larger. These larger icons are already more recognizable and it becomes easier and quicker to learn which items correspond to which icons.

    Toolbox

    If someone prefers the older dock panel, you can create a custom end-user designer form with the help of the XRDesignDockManager component.

    JMB: I concur. As an example, I use Adobe Illustrator infrequently but regularly, and I certainly don't need the text to go along with the toolbox icons. I've learned enough about the environment that I'm familiar with the 20% I use and I know I can always hover to get the tooltips to learn about the other 80%.

    Anyway, Konstantin, thank you for taking the time to explain about the new refreshing look of the XtraReports report designer. I think we'll have to migrate some of your changes over to our other control designers...

    Konstantin: No problem, I'll help out for sure. And it was fun to explain what we've done for v2009.3.

More Posts Next page »
Copyright © 1998-2010 Developer Express Inc.
ALL RIGHTS RESERVED