My Master's Thesis Problems and solutions encountered…

31Oct/100

Cascading Comboboxes in MVVM

Cascading comboboxes refers to having a combobox being populated depending on the selected item of another combobox. In my example, I have what is called a Group Questionnaire, which can contain any number of Questionnaires.

The main purpose with this post is to show how to deal with the Silverlight bug that entails that the binding of the SelectedItem to the second combobox is broken the second time the first combobox is updated, if it is bound to an ObservableCollection.

The actual populating of the Cascading comboboxes is not at all different than when a combobox is populated normally, but I will show it very quickly anyway.

The first thing you need to do is create a new class that will inherit the normal ComboBox:

Step 1: Create the following class in your ViewModel, which inherits the ComboBox.


  public class XComboBox : ComboBox
    {
        private BindingExpression bE;
        public XComboBox()
        {
            this.SelectionChanged += new SelectionChangedEventHandler(XComboBox_SelectionChanged);
        }

        void XComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (bE==null)
            {
               bE = this.GetBindingExpression(ComboBox.SelectedValueProperty);
            }
            else
            {
                if (this.GetBindingExpression(ComboBox.SelectedValueProperty) == null)
                {
                   this.SetBinding(ComboBox.SelectedValueProperty, bE.ParentBinding);
                }
            }
        }

    }

Step 2: In your View, where you want to add the cascading comboboxes, add a reference to the ViewModel, so you can access the XCombobox. Notice that the reference is called NewCombo:


xmlns:NewCombo="clr-namespace:ViewModel;assembly=ViewModel"

Step 3: Add the combobox in the View. Notice the NewCombo:XComboBox:

<!-- FIRST COMBOBOX -->
<NewCombo:XComboBox  x:Name="AllParentQstionnaires"
        ItemsSource="{Binding GroupQuestionnaireCB, Mode=OneWay}"
       SelectedValue="{Binding SelectedGroupQuestionnaireCB, Mode=TwoWay}"
       DisplayMemberPath="ParentQuestionnaireName"
       SelectedValuePath="ParentQuestionnaireID"
                                  />

<!-- SECOND COMBOBOX -->
   <NewCombo:XComboBox  x:Name="AllQuestionnaires"
       ItemsSource="{Binding QuestionnaireCB, Mode=OneWay}"
       SelectedValue="{Binding SelectedQuestionnaireCB, Mode=TwoWay}"
       DisplayMemberPath="QuestionnaireName"
       SelectedValuePath="QuestionnaireID"
                            />

And that's basically it! The rest works as usual, and you will want to create the properties of the ComboBoxes in the ViewModel.

Notice that the two comboboxes are bound to the GroupQuestionnaireCB and the QuestionnaireCB.

Step 4: Create all the properties for the comboboxes, and populate the first in the constructor, and the second combobox in the setter of the first combobox.


using ViewModel;
using ViewModel.QMServiceReference; 

namespace ViewModel
{
    public class Client_Questionnaire3_ViewModel : INotifyPropertyChanged
    {

        QMServiceReference.Service1Client WebService; 

        #region combobox properties

        //First combobox
        private ObservableCollection<ParentQuestionnaire> groupQuestionnaireCB;
        public ObservableCollection<ParentQuestionnaire> GroupQuestionnaireCB
        {
            get
            {
                return groupQuestionnaireCB;
            }
            set
            {

                groupQuestionnaireCB = value;
                RaisePropertyChanged("GroupQuestionnaireCB");

            }

        }

        private int selectedGroupQuestionnaireCB;
        public int SelectedGroupQuestionnaireCB
        {
            get
            {

                return selectedGroupQuestionnaireCB;
            }
            set
            {
                selectedGroupQuestionnaireCB = value;
                //Vi populate combobox nr. 2 with the SelectedValue from combobox 1. 

                RaisePropertyChanged("SelectedGroupQuestionnaireCB");
                //Populate next combobox
                WebService.GetAllQuestionnairesCompleted += new EventHandler<GetAllQuestionnairesCompletedEventArgs>(WebService_GetAllQuestionnairesCompleted);
                WebService.GetAllQuestionnairesAsync(2, SelectedGroupQuestionnaireCB);
            }
        }

        //Second combobox

        private ObservableCollection<AllQuestionnaires> questionnaireCB;
        public ObservableCollection<AllQuestionnaires> QuestionnaireCB
        {
            get
            {
                return questionnaireCB;
            }
            set
            {
                questionnaireCB = value;
                RaisePropertyChanged("QuestionnaireCB");
            }
        }

        private int selectedQuestionnaireCB;
        public int SelectedQuestionnaireCB
        {
            get
            {
                return selectedQuestionnaireCB;
            }
            set
            {
                selectedQuestionnaireCB = value;
                RaisePropertyChanged("SelectedQuestionnaireCB");

            }

        }

        #endregion

        //Contructor

        public Client_Questionnaire3_ViewModel()
        {

                WebService = new Service1Client();
                 //calls webservice to populate the first combobox
                WebService.GetParentQuestionnaireCompleted += new EventHandler<GetParentQuestionnaireCompletedEventArgs>(WebService_GetParentQuestionnaireCompleted);
                WebService.GetParentQuestionnaireAsync(2); 

        }

        void WebService_GetParentQuestionnaireCompleted(object sender, GetParentQuestionnaireCompletedEventArgs e)

     //populates the first combobox
       {
            GroupQuestionnaireCB = e.Result;
            //selects the first value as deafult
            if (GroupQuestionnaireCB.Count > 0)
            {
             SelectedGroupQuestionnaireCB = GroupQuestionnaireCB[0].ParentQuestionnaireID;
            }
        }

        void WebService_GetAllQuestionnairesCompleted(object sender, GetAllQuestionnairesCompletedEventArgs e){
        //populates the second combobox
            QuestionnaireCB = e.Result;
        //selects the first value as default
            if (QuestionnaireCB.Count > 0)
            {
                SelectedQuestionnaireCB = QuestionnaireCB[0].QuestionnaireID;
            }

        }

//Eventhandler
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string propertyname)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyname));
            }
        }

  }

}

And that's it!

Well, I have not shown the whole Model aspect, but it it's completely like described in my previous post.

7Aug/106

CRUD operations in Silverlight Datagrid – MVVM

This post will explain how you can add, edit or delete data from the database, by having it inserted into a Datagrid in your Silverlight application, and editing it from there.

The premise for following the guide in this post is that you have a project with the MVVM design pattern, and that you have already created a Datagrid that is populated with data from a database. Furthermore, you should be willing to open ChildWindows using the slightly un-MVVM'ish method described in my last post. If anybody wants to follow the guide I provide in this post, it is definitely recommended first to have followed the guide on how to open ChildWindows, although the main aspects of it also are covered here.

Now, I use my grid in order to display questions that are used for a questionnaire, that determines whether a company is eligible for becoming certified for the standard SA8000, ie. whether their working conditions are acceptable. Therefore, the questions in the example are all related to working conditions. The colors each question is given represents the severity associated with a company answering 'yes' or 'no' to the question (obviously, the companies filling out the questionnaire can't see the colors - it's just used afterwards, to get an overview over how well the company did).

To simplify the example, I will only post the code related to editing of the question. The add and delete methods are very much alike, and after having gone through the how-to, you can probably figure out how to change what needs to be changed. If not, drop a comment and I'll add it. To simplify further, I will not add the editing of the groups or the colors.

My whole grid looks like this:

MVVM edit/add/delete data in datagrid

So, what I wish to achieve is this: When I select a row from the grid and press the edit button, a child window has to appear with the data from the selected row inserted into an editable textbox. When I hit the save button in the child window, the database has to be updated, and the datagrid in the main window has to be updated as well, having inserted the changes from the database.

To sum up, I want my ChildWindow to look like this:

Again, you can disregard the colors and the group section, as I will only be focusing on the textbox with the question.

OBS: All code for both the MainPage and the ChildWindow are in the ViewModel for the MainPage.

So. From the start:

Step 1: In the ViewModel for you MainPage, you'll add properties for the grid, the selected row in the grid, the question in the mainpage, and the question in the ChildWindow. Notice that the SelectedQuestion property for the MainPage is set in the SelectedQuestionRow which is updated everytime the user clicks on a new row in the grid.

Notice that the object I will populate my grid with is called QuestionGrid, which was of course defined in the Model. You'll just have to update this to whatever you named yours.


//The property that will be tied to the ItemSource in XAML
   private QuestionGrid _questionGrid;
        public QuestionGrid QuestionGrid
        {
            get { return _questionGrid; }
            set
            {
                _questionGrid = value;
                RaisePropertyChanged("QuestionGrid");
            }

        }

        //Selected row.  THe property that will be binded to SelectedItem in XAML.
        private QuestionGrid _selectedQuestionRow;
        public QuestionGrid SelectedQuestionRow
        {
            get
            {
                return _selectedQuestionRow;
            }
            set
            {

                _selectedQuestionRow = value;

 //sets the value for the string that will be passed to the childwindow
                SelectedQuestion = value.Question;

 //We want to retrieve the ID as well, for when we update the database
               SelectedQuestionID = value.Q_UI; 

            }
        }
//QuestionID, MainPage
  public int selectedQuestionID;
        public int SelectedQuestionID
        {
            get
            {
                return selectedQuestionID;
            }
            set
            {
                selectedQuestionID = value;
                RaisePropertyChanged("SelectedQuestionID");
            }
        }

//Question for mainpage
        public string selectedQuestion;
        public string SelectedQuestion
        {
            get
            {
                return selectedQuestion;
            }
            set
            {
                selectedQuestion = value;
                RaisePropertyChanged("SelectedQuestion");
            }
        }

 //Properties for the grid. 

   //The question

            private string question_EditCW;
            public string Question_EditCW
            {
                get { return question_EditCW; }
                set
                {
                    question_EditCW = value;
                    RaisePropertyChanged("Question_EditCW");
  //SaveEdit is the name of the command we will create later, to save the changes to the database
                    SaveEdit.RaiseCanExecuteChanged();

                }
            }  

So, now I have created the properties, and now we will define the xaml to both the MainPage and the ChildWindow:

Step 2: Create the grid and button in the MainPage (the command binded to the button will open the child window)

Notice that the grid binds to QuestionGrid and SelectedQuestionRow, created earlier, but also notice that the question itself in the Textblock in the gird is binded to the attribute name of the object, created in the Model.


<!-- MainPage: DATAGRID -->

 <data:DataGrid x:Name="AllCompaniesGrid"
                SelectedItem="{Binding SelectedQuestionRow, Mode=TwoWay}"
                ItemsSource="{Binding QuestionGrid}"   

                AutoGenerateColumns="False"
                VerticalScrollBarVisibility="Visible" >

    <data:DataGrid.Columns>
        <!--Question Column. -->
        <data:DataGridTemplateColumn Header="Questions">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Question}" />
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
 </data:DataGrid>

<!-- The button! -->

 <Button Content="Edit" Style="{StaticResource Knap1}" Width="60" Command="{Binding EditQuestion}"  />

Step 3: Create textbox and button in the ChildWindow (the command binded to the button will update the database, close the window, and update the grid).


<!-- The textbox -->
 <TextBox Text="{Binding Question_EditCW, Mode=TwoWay}"     />

<!-- The button! -->

  <Button x:Name="Add" Command="{Binding SaveEdit}" Content="Editquestion"  />

Step 4: Define the command for button in the MainPage, in the ViewModel. I will go through this very quickly, but look at my old post here, if you have doubts about the DelegateCommand, or anything else.

But remember: The delegate command is binded to a button and is divided into three parts: 1) The property part that creates the property that will bind to the edit button, and calls the CanExecute and Execute methods, 2) the CanExecute part that tells wether the button is clickable or not (returns true or false), and 3) the Execute part, that executes if the button is pressable and is pressed: In our case, the Execute will open the new ChildWindow, called EditQuestionCW.


//property for the button command
            private DelegateCommand editQuestion;
            public DelegateCommand EditQuestion
            {
                get
                {
                    if (editQuestion == null)
                        editQuestion = new DelegateCommand(executeEditQuestion, canExecuteEditQuestion);
                    return editQuestion;
                }
            }
//Is the button clickaable? True = yes, False = no.
            private bool canExecuteEditQuestion(object parameter)
            { //Button only clickable if row in grid is selected
                if (SelectedQuestion != null) { return true; }
                else { return false; }
            }

            //New instance of the childwindow. My ChildWindow EditQuestionCW is in the folder ChildWindows.
            ChildWindows.EditQuestionCW EditQuestionCW;
            private void executeEditQuestion(object parameter)
            {

                //Sets the question
                Question_EditCW = SelectedQuestion;
                RedYes_EditCW = true;

                //Opens Child Window
                EditQuestionCW = new ChildWindows.EditQuestionCW(this);
                EditQuestionCW.Show();
           }

These next steps differentiates this post from the former. Here we will define what happens, when the SaveEdit button is pressed in the childWindow, and therefore how we update the changes to the database. But first: Creating the SQL query.

Step 5: Create the SQL query that will update the database and insert the new data. This will happen in your Model . Mine looks somethink like this:

public class EditAddQuestionDA : DBConnection
    {
  public void EditQuestion(int q_ID, string question) //The id and the question as parameters
        {
            SqlConnection dbConn = connectToDB();
            SqlCommand cmd;
            string updateQuestion = string.Format("update question set question = '" + question + "' where q_ID = " + q_ID + "; "); 

            try
            {
                dbConn.Open();

                if (question != "")
                {
                    cmd = new SqlCommand(updateQuestion, dbConn);
                    cmd.ExecuteNonQuery();
                }
                dbConn.Close();
            }
            catch (Exception e) { Console.WriteLine(e.ToString()); }

        }
}

Step 6: In the Service1.svc.cs make the method available to the ViewModel:

 public void EditQuestion(int q_ID, string question)
        {
            EditAddQuestionDA allDA = new EditAddQuestionDA();
            allDA.EditQuestion(q_ID, question);
        }

Step 7: In the IService1.cs:

 [ServiceContract]
    public interface IService1
    {
  [OperationContract]
        void EditQuestion(int q_ID, int G_ID, string question, int yes, int no);
}

So, now the query can be accessed from the ViewModel, so we can create the DelegateCommand for the button in the ChildWindow, that will call the EditQuestion() method:

Step 8: In the ViewModel we create the SaveEdit DelegateCommand, that is divided - like before - to three parts: 1) the DelegateCommand SaveEdit property, 2) The boolean method that determines if the button is clickable, and 3) The Execution of the code that will save the new question to the database, and update the grid in the MainPage.


      private DelegateCommand saveEdit;
            public DelegateCommand SaveEdit
            {
                get
                {
                    if (saveEdit == null)
                        saveEdit = new DelegateCommand(executeSaveEdit, canSaveEDit);
                    return saveEdit;
                }
            }

//Can Execute - Defines if the button should be clickable.
//True = Yes, false = no. Yes if the question is not identical to the one in the grid = no changes made
            private bool canSaveEDit(object parameter) //Definerer om knappen er klik-bar.
            {

              if (SelectedQuestion == Question_EditCW)
                {
                    return false;  //Only clickable if changes have been made
                }

                else { return true; } //Not identical -> return true
            }

//When the button is pressed:
            private void executeSaveEdit(object parameter)
            {
                //updates the grid (only visually, not in the DB
                SelectedQuestionRow.Question = Question_EditCW;

                //WE cose the childwindow, and call the method that will insert the new question into the database

                EditQuestionCW.Close();

                QMServiceReference.Service1Client WebService.EditQuestionCompleted += new EventHandler<AsyncCompletedEventArgs>(WebService_EditQuestionCompleted); //Defines the method that will be called when the database has been called.
                WebService.EditQuestionAsync(SelectedQuestionID, Question_EditCW); //Calls the method

            }

//The method that is called when the database has been updated: 

  void WebService_EditQuestionCompleted(object sender, AsyncCompletedEventArgs e)
        {
           //When the database has been updated we update the grid in the MainPage. 

            WebService.GetQuestionGridCompleted += new EventHandler<GetQuestionGridCompletedEventArgs>(WebService_GetQuestionGridCompleted);
            WebService.GetQuestionGridAsync(SelectedQuestionnaireCB);

        }

//When the GetQuestionGrid query has been run - insert the results into the grid: 

        void WebService_GetQuestionGridCompleted(object sender, GetQuestionGridCompletedEventArgs e)
        {

            QuestionGrid = e.Result;
        }

Enjoy!

Filed under: C#, MVVM, Silverlight 4, XAML 6 Comments
13May/100

Passing Data and Uri Mapping

It's very simple to pass data from one Silverlight page to another:

Say you have an AutoCompleteBox and a Button on PageA.xaml, and when you press the button, you want to 1) reach PageB.xaml, and 2) print the content of the AutoCompleteBox in to a TextBlock in PageB.xaml.

Step 1: Insert the AutoCompleteBox and a Button. Notice the x:Name, and Click:


<input:AutoCompleteBox x:Name="Supplier"
                            IsTextCompletionEnabled ="True"
                            />

<Button Content="Search"
            Click="Button_Click"/>

Step 2: Go to PageA.xaml.cs and create the navigate method:

void Button_Click(object sender, RoutedEventArgs e)
        {

            if (Supplier.Text != "") //if user has inserted anything in the AutoCompleteBox
            {

                string supplier = Supplier.Text.ToString(); //Supplier = name of AutoCompleteBox
                NavigationService.Navigate(new Uri(string.Format("/PageB.xaml?Supplier={0}", supplier), UriKind.Relative));
            }
            else
            {
            MessageBox.Show("Enter something");
            }
        }

Step 3: Create PageB.xaml and make a new TextBlock that will contain the content retrived from the AutoCompleteBox on PageA:

<TextBlock x:Name="Supplier_name"/>
 

Step 4: Go to PageB.xaml.cs and create the method that will accept the value from PageA and insert it into the TextBlock.


protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            string supplier = NavigationContext.QueryString["Supplier"].ToString();
            Supplier_name.Text = supplier;
        }
 

This will work perfectly, but you will notice that your URL doesn't look very pretty when you reach PageB. So, we will use Uri Mapping to define what we want the URL to look like.

Uri Mapping

Step 1: Go to your App.xaml and insert xml namespace for the navigation framework at the top:

xmlns:nav="clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation">
 

Step 2: Go to PageA.xaml and change your Button_Click method. Inside the string.Format() you add what you want the URL to be called, followed by the value you pass:


string supplier = Supplier.Text.ToString();
NavigationService.Navigate(new Uri(string.Format("PageB/{0}", supplier), UriKind.Relative));
 

Step 3: Add a new UriMapper to the Application Ressources. The Uri represents what the URL will look like, and the MappedUri where the button links to:

 <Application.Resources>
        <nav:UriMapper x:Key="uriMapper">
                <nav:UriMapping Uri="PageB/{c}" MappedUri="/PageB.xaml?Supplier={c}" />
        </nav:UriMapper>
 </Application.Resources>
 

Notice that the string.Format("PageB/{0}"... is identical to the Uri="PageB/{c}", except the value inside the brackets.

Step 4: Go to your Navigation Framework (probably located at your MainPage.xaml), and add the UriMapper property to the Navigation Frame (see line 5):

 <navigation:Frame x:Name="ContentFrame"
                              Grid.Row="1"
                              Grid.Column="1"
                              Source="/Customer_Home.xaml"
                              UriMapper="{StaticResource uriMapper}"
 />
 

After running this code you'll see, that the URL is called "...PageB/ followed by the value entered in the AutoCompleteBox.

11May/100

DataGrid Control

The DataGrid property allows you to list large amounts of data within a table. With Silverlight, the grid contains rich user functionality, including automatic resizing, reordering and auto-generating of columns.

This post is only relevant, if you want to hardcode data and insert it into a grid. If you want to get the data from a database, you'll need to do it trough a Web Service. See my post called Accessing Data Through Web Services to learn more about this.

Step 1: You need to create a reference to System.Windows.Controls.Data, by right-clicking on your project >> Add Reference >> .Net >> code>System.Windows.Controls.Data. Then, insert the following xmlns at the top of your .xaml page, where you will be inserting your Grid:


xmlns:Data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"

Step 2: Insert the DataGrid into your .xaml page.

Notice the IsReadOnly property:


 <data:DataGrid x:Name="dg" IsReadOnly="True" Margin="10"/>

Step 3: Create a new class by right-clicking on the project >> Add >> Class. Populate the DataGrid by building a class that will be bound to the DataGrid. We define a few properties to bind to, and these represent the columns that the DataGrid will contain.

I called the class Data.cs and inserted the following code:


public class Data
    {
        public string Name { get; set; }
        public int Green { get; set; }
        public int Yellow { get; set; }
        public int Red { get; set; }
        public int NA { get; set; }
        public string Certification { get; set; }

    }

Step 4: In the code-behind for the .xaml page that contains your DataGrid, insert the code shown below.

Notice that the List< Data> on line 5 refers to the class, and the dg in the dg.ItemsSource refers to the name you have given the DataGrid.

public Page()
        {
            InitializeComponent();

            List<Data> source = new List<Data>();
            int itemsCount = 15;

            for (int i = 1; i < itemsCount; i++)
            {

                source.Add(new Data()
                {
                    Name = "Company " + i,
                    Green = i + 20 - 7,
                    Yellow = 20 + 7 - i,
                    Red = 0,
                    NA = 0,
                    Certification = "test" + i
                });
            }

            dg.ItemsSource = source;
}

That's it!

Of course, the loop is just practical for creating rows and content fast. In any other case, just delete the loop and repeat the Source.Add(newData) method as many times as needed.

By following the steps above, you'll get a DataGrid like the one shown below:

11May/100

AutoCompleteBox

When inserting a search function within your application, it is useful to use the AutoCompleteBox if you already know what the user will search for. In the case of my application, the user can only search for a specific set of company names, and so the AutoCompleteBox is useful.

Like in the previous post, I will hardcode the set data into the code, but this will later be changed so it collects the names from a MySQL database.

First, you need to create a reference to System.Windows.Controls and to System.Windows.Controls.Input. You do this by right-clicking on your project >> Add reference >> Choose System.Windows.Controls, and System.Windows.Controls.Input. If you do not add the reference, you'll be presented with an error message saying "Verify that you are not missing an assembly reference".

Next, you need to add a an xmlns entry at the top of your .xaml page.


xmlns:input="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input"

Then you just insert your new AutoCompleteBox. Something like this (notice the IsTextCompletionEnabled):


  <input:AutoCompleteBox x:Name="Supplier"
                          Width="220"
                          Height="20"
                          Margin="10"
                          IsTextCompletionEnabled ="True"/>

The IsTextCompletionEnabled enables the automatic completion of the text as you type. Its default is set to False, so if you do not wish it to auto-complete, then do not add the property.

In the code-behind, you can bind a simple string array to the ItemsSource property:


 public Customer_Find_Supplier()
        {
            InitializeComponent();
            this.Supplier.ItemsSource = new string[]
            {
            "Company One", "Company Two", "Company three"
            };
        }

And that's it!

Filed under: C#, Silverlight, XAML No Comments
10May/100

Linking from User Control to User Control

As I showed in a recent post, it is possible to link from a Silverlight User Control to a Silverlight page using the Silverlight Navigation Framework. But at times you will need to link from a User Control to another User Control, and there you won't be able to use it.

In my case, I encoutered this situation because my application has three different user types, who each have their own Navigation Framework. They have this because they each have vastly different tasks to perform, and thus there's need of different menus to access different pages. But there is one task that they must all carry out: Before any user can access the application, they must log in. Of course, the login page is a User Control, so I needed to link the Login page to one of the Navigation Framework user pages, which are also User Controls.

Basically, you will need too add a small code snippet outside the Grid on the page you wish to link from (in my case, the login-page), and an event EventHandler to a button. In the EventHandler you specify which grids are visible, and which are not.

Below, I have posted a very simplified version of my MainPage.xaml, that I use for the login. Notice that there are two grids at the top, and that we have named the inner grid Login, and its Visibility is set to Visible:


<UserControl x:Class="QM_v1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d"
xmlns:local="clr-namespace:QM_v1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="437" d:DesignWidth="947">
<!--Background layout-->
<Grid x:Name="LayoutRoot">

   <Grid x:Name="Login" Background="White" Width="Auto" Height="auto" ShowGridLines="False" Visibility="Visible">

  <!-- Focus on the 'Enter' button -->

           <TextBox x:Name="Mail">
           <PasswordBox x:Name="Pass" >

            <StackPanel Grid.Row="2" Grid.Column="2" Orientation="Horizontal">
                <Button Content="Cancel" Click="Cancel_button"/>
                <Button Content="Enter" Click="Enter_button"/>

            </StackPanel>

   </Grid>

<!-- Important bit: -->
<local:Customer x:Name="Customer" Visibility="Collapsed"/>
<local:Supplier x:Name="Supplier" Visibility="Collapsed"/>
<local:ETS x:Name="ETS" Visibility="Collapsed"/>
</Grid>
</UserControl>

Notice that the local:Customer bit is outside the second grid. In here you specify the name of the .xaml pages that you wish to link to, depending on which user logs in. Here I have just specified that I have three .xaml pages (Customer, supplier and ETS), and that each of these pages at this point in time are collapsed, ie. not visible/active.

When the user presses the Enter_Button you want your EventHandler to check who the user is, and depending on the outcome they should be directed to either customer.xaml, supplier.xaml or ETS.xaml. For simplicity, I have hardcoded the user name and password, but they should of course be gathered from a database.


private void Enter_button(object sender, RoutedEventArgs e)
{

   if (Mail.Text != "" && Pass.Password != "")//there is some content.
   {

     if (Mail.Text == "customer@gmail.com" && Pass.Password == "customer")
     {
        Customer.Visibility = System.Windows.Visibility.Visible; //customer = Name of .xaml page
         Login.Visibility = System.Windows.Visibility.Collapsed; //Login = inner grid at MainPage.xaml
     }
     else if (Mail.Text == "supplier@gmail.com" && Pass.Password == "supplier")
     {
      Supplier.Visibility = System.Windows.Visibility.Visible;
      Login.Visibility = System.Windows.Visibility.Collapsed;
     }
     else if (Mail.Text == "ets@gmail.com" && Pass.Password == "ets")
     {
     ETS.Visibility = System.Windows.Visibility.Visible;
     Login.Visibility = System.Windows.Visibility.Collapsed;
     }
     else
     {
     MessageBox.Show("Please enter correct e-mail and password.");
     }
  }
  else
  {
  MessageBox.Show("Please type your e-mail and password.", "Error", MessageBoxButton.OK);
  }
}

Notice that the two things happen: 1) The users' own page is set to visible (Customer.Visibility = System.Windows.Visibility.Visible;) and the current page, the MainPages' Login-grid, is closed/collapsed (Login.Visibility = System.Windows.Visibility.Collapsed;).

VoilĂ !

Filed under: C#, Silverlight, XAML No Comments
8May/100

Adding Images

Here is just a quick guide to inserting images into your Silverlight application, and how to make them link to other pages. In this example I will just hardcode the name of the image, but I will soon write another post about how to insert images dynamically.

First, you need to create an images folder. If the name of your application is MyApp, go to the ClientBin folder located at MyApp >> MyApp.Web >> ClientBin, and create the images folder.

The following simple code snippet shows how you insert an image located in your images folder.


<Image Source="../images/picture.jpg" Height="100" />

If you want your image to link to another Silverlight page, you simply create the image as an HypelinkButton:


<HyperlinkButton HorizontalAlignment="Center" NavigateUri="/mypage.xaml">
        <HyperlinkButton.Content>
              <Image Source="../images/picture.jpg" Height="100" />
        </HyperlinkButton.Content>
</HyperlinkButton>

Oh, and remember that Silverlight only supports .png and .jpg images.

Filed under: Silverlight, XAML No Comments
5May/101

Views – Navigation Framework

When developing your Silverlight application you will almost always need to create several pages and subpages. Thus, you will need a navigation framework. Silverlight allows you to browse between different pages while enabling forward and backward navigation through the history using the browser's back and forward buttons.

This post provides a complete walk trough on how to implement a menu in your Silverlight application, and how to create and access subpages. You will end up with something like the application showed below (but without the formatting):

Navigation Framework - Silverlight - Menu

The working application is temporarily hosted here.

Step 1: Add support for the Navigation Framework by adding a reference to System.Windows.Controls.Navigation.dll by right clicking on the References folder in your Silverlight Project, and choosing Add reference.

Reference

Step 2: Go to the .Net tab, and select the System.Windows.Controls.Navigation, as seen below:

Add Reference

Notice that it says "Filtered to: Silverlight 4" at the top. If you happen to have converted your Silverlight application from Silverlight 3, then it will say "Filtered to: Silverlight 3", and you will need to update this by right-clicking on you project >> Properties >> and set Target Silverlight Application to "Silverlight 4".

Step 3: You will need to add System.Windows.Controls.Navigations to your UserControl definition. Insert the following code at the top of your MainPage.xaml:


    xmlns:navigation ="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"

Step 4: Create a StackPanel within your Grid in your MainPage.xaml, and insert HyperlinkButtons, that will be used as the menu. Something like this:

<!--Menu -->
            <StackPanel Orientation="Horizontal"
                            HorizontalAlignment="Center"
                            VerticalAlignment="Bottom"
                            Grid.Column="1"
                            Grid.Row="0">

                <HyperlinkButton Content="Home"
                                 Click="LinkClick"
                                 Tag="/Home.xaml"
                                 Padding="5"/>
                <HyperlinkButton Content="Find supplier"
                                 Click="LinkClick"
                                 Tag="/Find_Supplier.xaml"
                                 Padding="5"/>
                <HyperlinkButton Content="Questionnaire data"
                                 Click="LinkClick"
                                 Tag="/Options.xaml"
                                 Padding="5"/>
            </StackPanel>
<!--Navigation Frame -->
            <navigation:Frame x:Name="ContentFrame"
                              Margin="10"
                              Grid.Row="1"
                              Grid.Column="1"
                              BorderBrush="Black"
                              BorderThickness="2"
                              Source="/Home.xaml"
                              />

Note:
Lines 2-6 define how the menu will look like, so you can of course change this as you please.
Line 10, 14, and 18: You will create each of these pages in the next step. Change the Tag so it matches the name of your subpages.
Line 9, 13, 17: The LinkClick-method is created later.
Line 28: The Source states the opening page, ie. the frontpage of your application.

The above code basically just creates the menu that the user will click on to navigate through the application. The XAML page you insert this into will not ever be shown: It is merely used to define the framework and the links to subpages. What you need to do next is actually create all the subpages, that will hold all content.

Step 5: Add a new page by right-clicking on your Project >> Add >> New Item >> New Page. It looks like this:

Add page

Remember to name the pages like you stated in the HypperlinkButton above.

Step 6: Open your new subpages and add simple Textblocks, preferably including the name of the page, so you can identify it when you run the program.

Step 7: The last step is to add a LinkClick method in your MainPage.xaml.cs.


 private void LinkClick(object sender, RoutedEventArgs e)
        {
            HyperlinkButton button = (HyperlinkButton)sender;
            string viewSource = button.Tag.ToString();
            ContentFrame.Navigate(new Uri(viewSource, UriKind.Relative));

        }

And that's it!

Inner Views

If you need to create a new page that is not one of the 'main' pages shown in the menu, you need to create an InnerView. You just do like shown above (you create a button with a Click property, but without the Tag property, and you create a new page). The main difference lies in step 7. You have to insert the code below instead:


 private void LinkClick(object sender, RoutedEventArgs e)
        {
           NavigationService.Navigate(new Uri ("/InnerView.xaml", Uri.Kind.Relative));

        }

Enjoy!

22Apr/100

External Links

Here is just a quick and small code snippet for linking to external pages from your Silverlight application.

I found quite a few potential solutions to the problem, but the first 2 or 3 just didn't compile. Most people recommended the Hyperlink with some sort of System.Diagnostics.Process.Start("http://www.link.com");attached, but I simply couldn't make it work. But finally I found a solution:

Insert a TextBlock like the one below. Notice the MouseLeftButtonDown.


            <TextBlock Grid.Column="2" Grid.Row="0"
                       VerticalAlignment="Top"
                       Text="www.aook.dk/blog"
                       TextDecorations="Underline"
                       MouseLeftButtonDown="TextBlock_MouseEnter"
                       HorizontalAlignment="Right"
             />

Next, you just need to create an EventHandler like the one below, but make sure to first add an using System.Windows.Browser; at the top.

        private void TextBlock_MouseEnter(object sender, MouseButtonEventArgs e)
        {
            HtmlPage.Window.Navigate(new Uri("http://www.aook.dk/blog"));

        }

Enjoy!

Filed under: C#, Silverlight, XAML No Comments
19Apr/100

Input validation

I needed to create a login feature, so only registered users could gain access to the Silverlight application, and I wanted to create a data validator, that would check the users' input. In the this example I just check if the user has entered an e-mail address.

Red lineError messageNo error
In the first image to the left, I have just entered my name in the E-mail address field, and entered my password. The textbox is marked with red, because there has not been registered an "@" in the field (of course, this is a very simple example. One could easily find more thorough ways of validating an e-mail address, which I will probably make a blog post about some time soon).

In the second image, the user has selected the textbox, and immediately an error message appears, explaining the user why there is a problem.

In the third image, the user has inserted a valid e-mail address and can proceed with the login feature.

So, the way to implement this feature is pretty straight-forward, especially since there is a brilliant video tutorial showing exactly how to do it. I just followed the steps on the tutorial, but I will nonetheless quickly go through the code here.

The first thing you need to know is that - as brilliant as the tutorial mentioned above may be - the code will not compile if you have the default settings on Visual Studio 2008. The tutorial does not explain that you need to make one quick settings change in order to avoid an "Arguement Exception was unhandled by User"-error message, that prevents the code from compiling.

You need to the following: Go to Visual Studio Options and then Debugging/General, and just uncheck the 'Enable Just my Code'. And that's it.

The tutorial also fails to mention a small piece of code necessary to compile, but Visual Studio helps by telling you where you need to insert the following code:

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

We'll come back to this.

How to implement the input validation:

Open your MainPage.xaml, and find the textbox whose input you want to validate. Then insert Binding statements into TextBox.Text like shown below. Notice the Path called Mail. We will use this name later.


  <TextBox width = "390" >
           <TextBox.Text>
                <Binding Mode="TwoWay"
                        Path="Mail"
                        ValidatesOnExceptions="True"
                        NotifyOnValidationError="True"
                />
          </TextBox.Text>
 </TextBox>

So, we now have a textbox that needs to be binded to a data object, so we can check when there are any changes made to it. We do this by creating a new class: Right click on your application name in the Solutions Explorer, choose Add and Class. I named my class validation.cs.

The first thing to do is to insert using System.ComponentModel; at the top, and you can delete all others system statements except the using System;. This supports the INotifyPropertyChanged, that ensures two-way binding, that is, checks if any changes are made to the object, and if there is, then the UI is updated accordingly.

Insert the following code into your validation.cs:

using System;
using System.ComponentModel;

namespace QM_v1
{
    public class validation : INotifyPropertyChanged
    {
        private string mail;

        public string Mail
        {
            get { return mail; }
            set {
                if (value.IndexOf('@') == -1)
                {
                    throw new ArgumentException("This is not a valid email address");
                }
                NotifyPropertyChanged("Mail"); // The code crashes here, if you don't change your settings, as described above.

                mail = value; }
        }

        public event PropertyChangedEventHandler propertyChanged; //mandatory event for this class. Checks the object for changes.

        private void NotifyPropertyChanged(string propertyName)
        {
                if (propertyChanged != null)
                {
                    propertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
}

Notice lines 33-37: This code snippet is needed for the program to compile, but is not mentioned in the tutorial mentioned above.
Lastly, open your MainPage.xaml.cs and enter the following code in your class:

  public MainPage()
        {
            InitializeComponent();

            var m = new validation();
            LayoutRoot.DataContext = m;  //LayoutRoot = name of your Grid.

        }

And that's it!

See the working example here.

Filed under: C#, Silverlight, XAML No Comments
11Apr/100

Defining Styles at Application Level

When you have many subpages containing buttons, textblocks, stackpanels, etc., it is convenient to define the styles at application level. This way, all objects have the same design, and if you need to change the look and feel of your template, you only need to change it one place, like you would do with CSS when working with HTML.

In your Silverlight application, open your App.xaml , which should look something like this:

<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 x:Class="QM_v1.App">

    <Application.Resources>

    </Application.Resources>

</Application>

So, in this example we want to define a shared style for all textblocks, so they all have the same color, font, margin, and so forth. Inside your Application Ressource you will define a new Style, width the Setter Property and Setter Value. Like this:


 <Application.Resources>

        <Style x:Key="Normal_text" TargetType="TextBlock">
            <Setter Property="Fontsize" Value="12"/>
            <Setter Property="FontFamily" Value="Verdana"/>
            <Setter Property="Foreground" Value="#FF696969"/>
            <Setter Property="Margin" Value="5"/>
            <Setter Property="TextWrapping" Value="Wrap"/>
        </Style>

    </Application.Resources>

The Key is the unique name of the Style, while with the TargetType you define which object you want to work with. The Property and Value sets the variables. Note that Visual Studio does not auto-compete inside the Setter, so you need to be careful when writing the names of the variables.

Having defined the style, you just need to write: Style = "{Static Resource Style_name}" when defining your Textblock:


              <TextBlock Text="This text has the style that was defined in the App.xaml.  "
                Style="{StaticResource Normal_text}"
               />

Of course, this method can be used with Grids, StackPanels, buttons... everything. Very useful!

Filed under: Silverlight, XAML No Comments
11Apr/101

Grid Controls – Rows with Variable Height

When making the layout for your application you will most often use Grid Controls and/or WrapPanels. I needed a layout to be a single box that was always centered vertically and had a variable sized height, depending on the amount of text inside the box.

Initially, you need to define the basic grid layout. Open your new Silverlight project, and select the MainPage.xaml. It probably looks something like this (depending on your Visual Studio version):


<UserControl x:Class="Test.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
  <Grid x:Name="LayoutRoot">

  </Grid>
</UserControl>

You will need to add some measurements to the Grid definition, so it says:


<Grid x:Name="LayoutRoot" Background="White" Width="Auto" Height="Auto" ShowGridLines="True">

This only sets the color of the background and makes sure the size of the grid changes with the size of the window. Also, the ShowGridlines makes sure you can see the changes you will make next. When you are finished, you can set it to false.

With the grid control we create a 3 x 3 grid, and we need to specify the number and width/height of the columns and the rows. This is done in the Grid.RowDefinitions and Grid.ColumnDefinitions::

<UserControl x:Class="QM_v1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
  <!--Background layout-->
   <Grid x:Name="LayoutRoot" Background="White" Width="Auto" Height="auto" ShowGridLines="True">

       <!--We define the grid, ie. number of rows and number of columns-->
        <Grid.RowDefinitions>
            <RowDefinition Height="130"></RowDefinition>
            <RowDefinition Height="200"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="500"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>

    </Grid>
</UserControl>

As it can be seen, we have defined three columns, where only the middle as a fixed size. The first and third column will just occupy the space left in the window. The first two rows have initially a fixed height, while the last just occupies the remaining. If you run your program, it will look something like this:

The cell in the middle is where we will place our text (and eventually the whole application). You are maybe asking yourself why we defined a fixed height to the second row, when want it to be variable... This was just to show you how the grid works, and in the following, we set the second row height to auto. But we need to insert some text into this cell, otherwise the cell height i set to 0, and can therefore not be seen.

Changes to be noted in the code below:

  • RowDefinition Height has been changed for the second row, so it's now set to auto.
  • We have added a border that defines where the textblock is to be placed ( Grid.Column="1" & Grid.Row="1">), and a StackPanel, that defines the margin.
  • The new textblock inserts the texts, and its TextWrapping = "Wrap" ensures that the text is wrapped by the border.

<UserControl x:Class="QM_v1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
  <!--Background layout-->
   <Grid x:Name="LayoutRoot" Background="White" Width="Auto" Height="auto" ShowGridLines="true">

       <!--We define the grid, ie. number of rows and number of columns-->
        <Grid.RowDefinitions>
            <RowDefinition Height="130"></RowDefinition>
            <RowDefinition Height="auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
            <ColumnDefinition Width="500"></ColumnDefinition>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>

<!--the border-->
        <Border BorderThickness="2"
                    BorderBrush="#D1CACA"
                    Margin="10"
                    CornerRadius="10"
                    Background="#E8E8E8"
                    Grid.Column="1"
                    Grid.Row="1">
            <!--The text-->
            <StackPanel Margin="10">
                <TextBlock Text="The height of this row depends on the amount of text to be wrapped. Make sure you have your middle row set to 'auto' and the TextWrapping set to 'Wrap'."
                           Margin="5"
                           TextWrapping="Wrap"
                 /><!--Important to wrap the text!-->
            </StackPanel>
        </Border>
    </Grid>
</UserControl>

If you run this code, you'll see something like this:

Grid, variable row size

As said before, you can just set the ShowGridlines to false when you are finished.

So, by using the code above you have created a grid, where the middle cells has fixed width, but the height depends on the amount of text you have inserted into the Textblock.

Filed under: Silverlight, XAML 1 Comment