RecentComments

Comment RSS

Autosuggest Textbox for WPF

by Ioannis 1. July 2010 19:57

Here is an implementation of a nice Textbox in WPF that suggests possible values based on the current user’s input. It is different from WPF’s native editable Combobox since it does not load all possible values at initialization (saving time and memory). The Textbox calls a method that returns the suggested values as soon as it detects a specific amount of idle time in the user’s typing. This method will probably access the database using the value entered so far in the Textbox as the criterion for the suggested values.

The main idea is as follows:

We create a grid whose first row contains a textbox and the second row contains a Listbox. Now, when the textbox gets the focus and its text changes due to the user’s input, between the user’s key presses we start a countdown timer. If the timer reaches zero before the user pressing another button, we call a method to retrieve the suggested values and display the textbox with those values. Now, if the user selects one of them, then we fill the textbox with the appropriate selection.

The TTimer.cs file contains the TTimer class that creates a primitive countdown timer:

public class TTimer
    {
        private Timer timer;
        public event Action<int> DoSomething;
        private int _timesCalled = 0;
 
        public TTimer()
        { }
        public void Start(int PeriodInSeconds)
        {
            timer = new System.Threading.Timer(timer_Task, null, 0,
            PeriodInSeconds * 1000);
        }
        public void Stop()
        {
            if (timer!=null)
            timer.Dispose();
        }
        private void timer_Task(object State)
        {
            _timesCalled++;
            DoSomething(_timesCalled);
        }
    }

The Autosuggest Textbox is implemented in AutoSuggestTextBox.xaml(.cs) and it is called TextBoxAS. Its properties are:

IdleTime: Defines the time to wait for the ListBox to appear when the user is idle.

ListHeight: Is the height of the Listbox with the suggested values.

ValueMember: Is the property of the objects in the list that should appear in the Textbox if the object is selected (.ToString() is used if empty).

DisplayMember: The property of the objects in the list that is displayed in the list (.ToString() is used if empty).

SelectedValue: Is the selected value from the ListBox.

The rest of the implementation is catching events and responding accordingly. The main action behind the events are summarized in the following list:

 

  • TextBox – TextChanged: When the text of the Textbox is changed if the Listbox is visible (_suggestingIsActive is true) we hide it since a new cycle should begin otherwise (_suggestingIsActive=false) we reset the countdown.
  • ListBox – SelectionChnaged: When the user has selected something from the ListBox we set this value to the SelectedValue dependency property.
  • TextBox-LostKeyboradFocus: When the TextBox loses the focus it is either because the user clicked on a value in the ListBox or because the user has navigated away to another control. If the first is true then if the SelectedValue DP has something, we use it to set the value of the TextBox. If the second is true we try to see whether the value entered in the TextBox can help us set the SelectedValue (there is only one suggestion for the value). Otherwise we revert to the previous value.
  • TextBox-GotKeyboradFocus: Initializes the timer.

 

The TextBox defines the event OnGetSuggestions that can be used to set the method to be called when the control needs to get the suggestions for a specific text.

Typical usage of the control is as follows:

<Controls:TextBoxAS Name="ASTextBox" 
                    Width="233" Height="171" Canvas.Left="28" Canvas.Top="25"  
                    OnGetSuggestions="TextBoxAS_OnGetSuggestions" 
                    DisplayMember="FullDescription" ValueMember="Name" 
                    ListHeight="150" />

In order to allow the ListBox to open on top of other controls, those controls should be defined in xaml before the definition of the TextBox.

You can download the sources for the project here

Shout it

Domain Specific Languages with M - Part 3/3 Consuming the DSL at runtime in C#

by Ioannis 20. May 2010 07:52

In this last post of the small series we will see how we can load the syntax of a DSL in C# and then feed it with an input text that we need to be parsed. In this post we have already described how to write the DSL syntax and in this post we have seen how to decorate it with productions.

 

So following the process described in those posts we end up with a file containing the description of the syntax of our DSL, along with its productions (eg the file Renting_en.mg available for download in the zip file provided at the end of this post).

 

We open Visual Studio 2010 and create a new console application. We add two new references to the project (System.Dataflow and Microsoft.M).

 

First, we load the file that contains the syntax of the DSL grammar and create some sort of “compiler” with it:

Stream mgStream = File.OpenRead(@"<Path to file>\Renting_en.mg");
CompilationResults Compilation = Compiler.Compile(new CompilerOptions
{
   Sources ={new TextItem
             {
                 Name="RentGrammar",
                 Reader = new StreamReader(mgStream),
                 ContentType = TextItemType.MGrammar
             }
            }
 
});

Then we will create the parser for our DSL language:

Parser theParser = Compilation.ParserFactories["Renting.RentLanguage"].Create();
theParser.GraphBuilder = new NodeGraphBuilder();

Note that the name of the language “Renging.RentLanguage” is exactly the same with the one defined within the .mg file.

The only thing left is parsing the input text in the file “Charges_en.txt” and dumping the result to the Console. You will see that you will get all the productions as specified in the DSL grammar.

Node root = (Node)theParser.Parse(@"<Path to file>\Charges_en.txt", null);            
Console.Write(root.WriteToString());

From this point you can explore the root object and get whatever you like from the production tree created. This post concludes the small introduction to the M language. If you would like more on M feel free to contact me. The solution along with the parser and the demo input language file can be downloaded

here

 

Shout it

kick it on DotNetKicks.com

Domain Specific Languages with M - Part 2/3 Attributes and Productions

by Ioannis 4. May 2010 15:51

In the previous post we have covered the basics of the M language concerning the development of a Domain Specific Language (DSL). In this post we will extent the syntax of the described DSL by adding attributes and production rules.

On feature that is kinda interesting is the ability to use attributes to define syntax highliting to the left panel of Intellipad (where your input text appears). Those attributes decorate the tokens of the syntax and are introduced by the @{Classification["Keyword"]} , @{Classification["String"]} etc attributes.

 

module Renting
{
    language RentLanguage
    {
        interleave whitespace = (" " | "\r" | "\n")+ | Comment;
       
        syntax Main= Charges+;
        syntax Charges=ChargeStartToken "(" ChargeName ")" ChargeBody ChargeEndToken;
        syntax ChargeBody=ChargeRule+;
        syntax ChargeRule=ForToken PeriodAmount Period ChargeToken CurrencyAmount CurrencyToken "per" Period "."
                         |EventuallyToken ChargeToken CurrencyAmount CurrencyToken PerToken Period ".";
        token PeriodAmount=('0'..'9')+;
       
        @{Classification["Keyword"]} 
        token Period="week"|"weeks"|"month"|"months"|"days"|"day"|"year"|"years";
        token CurrencyAmount=('0'..'9')+ DecimalSeparator ('0'..'9')* | ('0'..'9')+;
        token ChargeName=QuotedIdentifier;
        token DecimalSeparator=",";
        @{Classification["Keyword"]} 
        token ChargeStartToken="Scenario";
        @{Classification["Keyword"]} 
        token ChargeEndToken="End";
        @{Classification["Keyword"]} 
        token ForToken="For";
        @{Classification["Keyword"]} 
        token EventuallyToken="Eventually";
        @{Classification["Keyword"]} 
        token CurrencyToken="$";
        @{Classification["Keyword"]} 
        token ChargeToken="charge";
        @{Classification["Keyword"]} 
        token PerToken="per";
        @{Classification["Comment"]} 
        token Comment = "//" !("\r" | "\n")+;
        @{Classification["String"]} 
        token QuotedIdentifier = '"' !('\r' | '\n' | '"')+ '"';
    }
}

 

Up to this point, we have written the syntax of the DSL and used Intellipad to verify that a demo input text is recognized as we would expect (see this post ). Then we have used attributes to provide syntax highlighting. Take for example the following snapshot of a successful parsing of the input renting scenario:

 

(Note the syntax highlighting on the left panel)

 

So your requirements guy can start writing his renting rules in Notepad, while you need to figure out a way to persist them in the database or on the fly calculate how much somebody should pay for an item by a renting scenario that will be written in the future. By specifying the DSL syntax, you have made the first step.  Next step is to produce, when parsing the input text, something more meaningful rather than the recognized literal you see on the right hand side of Intellipad.

To achieve this you need to decorate your syntax with “production rules”. Those rules are usually interpreted as: “when you match this syntax, instead of the usual output, produce this”. For example:

 

syntax Charges=ChargeStartToken "(" n:ChargeName ")" b:ChargeBody ChargeEndToken
                      => RentCharge{n,valuesof(b)};

 

In red, I have highlighted aliases for the needed parts. You start a production rule with the “=>” symbol. The production rule above is interpreted as follows: “When the syntax rule Charges is matched, output RentCharge, and within “{}”, output the production of ChargeName, followed by a comma and then the value of ChargeBody”.


The whole syntax, with the production rules that formulate the desired output is given below:
 

module Renting
{
    language RentLanguage
    {
        interleave whitespace = (" " | "\r" | "\n")+ | Comment;
 
        syntax Main = c:Charges+ =>RentCharges{valuesof(c)};
        syntax Charges = ChargeStartToken "(" n:ChargeName ")" b:ChargeBody ChargeEndToken =>RentCharge{n,valuesof(b)};
        syntax ChargeBody = r:ChargeRule+ =>{valuesof(r)};
        syntax ChargeRule = ForToken pa:PeriodAmount pr:Period ChargeToken ea:CurrencyAmount CurrencyToken "per" fp:Period "."
                        =>ChargeRule{pa,pr,ea,fp}
                                          |EventuallyToken ChargeToken ea:CurrencyAmount CurrencyToken PerToken fp:Period "."
                        =>ChargeRule{“-1”,”-1”,ea,fp}; 
       
        token PeriodAmount=('0'..'9')+;
        @{Classification["Keyword"]} 
        token Period="week"=>"w"|"weeks"=>"w"|"month"=>"m"|"months"=>"m"|"days"=>"d"|"day"=>"d"|"year"=>"y"|"years"=>"y";
        token CurrencyAmount=('0'..'9')+ DecimalSeparator ('0'..'9')* | ('0'..'9')+;
        token ChargeName=q:QuotedIdentifier=>q;
        token DecimalSeparator=",";
        @{Classification["Keyword"]} 
        token ChargeStartToken="Scenario";
        @{Classification["Keyword"]} 
        token ChargeEndToken="End";
        @{Classification["Keyword"]} 
        token ForToken="For";
        @{Classification["Keyword"]} 
        token EventuallyToken="Eventually";
        @{Classification["Keyword"]} 
        token CurrencyToken="$";
        @{Classification["Keyword"]} 
        token ChargeToken="charge";
        @{Classification["Keyword"]} 
        token PerToken="per";
        @{Classification["Comment"]} 
        token Comment = "//" !("\r" | "\n")+;
        @{Classification["String"]} 
        token QuotedIdentifier = '"' n:(!('\r' | '\n' | '"')+) '"'=>n;
    }
}

 

The best thing to do to understand the above is omit some production rules and experiment with the result. The grammar above, augmented with the production rules produces for the following input:
 

 

//Renting scenario 1
Scenario ("Monthly")
    For 1 month charge 10,00$ per day.
    For 2 months charge 5,50$ per week.
    Eventually charge 20$ per month.
End

 

The following output:

 

RentCharges{
 RentCharge{"Monthly",
    ChargeRule{"1","m","10,00","d"},
    ChargeRule{"2","m","5,50","w" },
    ChargeRule{ “-1”,”-1”,"20","m"} }
}

This is now in a form that can be more easily processed. And we will do exactly that using a program we will write in C#.

The grammar for English can be downloaded here and for Greek here.

Shout it

Tags:

Powered by BlogEngine.NET 1.5.0.7

Programming Blogs - BlogCatalog Blog Directory Add to Technorati Favorites

MVP Award

Ioannis Panagopoulos





This blog is using BlogEngine.Net and is hosted in the hoster below. I have not experienced any problems installing BlogEngine.Net in the host and I am satisfied with the host's response times. Therefore I recommend it.


DiscountASP Add