Random programming things I'd want to remember

Monday, July 30, 2012

Solving the size limitation of controls in Windows Phone 7


There is a limitation on a size of a control in Windows Phone 7 of 2048 pixels, and I needed to sometimes display a long list of items. Here is how I did that. First off, the XAML portion:

<ScrollViewer Tap="ScrollViewer_Tap" Name="AnswerScrollViewer" 
                          Grid.Row="1" Height="Auto">
    <StackPanel x:Name="AnswerScrollViewerStackPanel">
        <TextBlock x:Name="Answer"  
                      Width="430"
                      Text=""
                      Tag=""
                      FontSize="28" 
                      TextWrapping="Wrap" />
    </StackPanel>
</ScrollViewer>

There is a ScrollViewer that I use to scroll a long list. It has a StackPanel (because ScrollViewer can only have one control inside it, and I will use several controls to display a long list of text). Then the StackPanel has the "Answer" TextBlock control that is used when there is no need to display the long list.

And now, on to the magic. This is a part of the function that assigns the text to the text block.


//more on the MeasureSizeOfATextBlock() later
double futureAnswerTextBlockSize = MeasureSizeOfATextBlock(Answer, Answer.Tag.ToString());

//MAXCONTROLHEIGHT is a constant, close to 2048
if (futureAnswerTextBlockSize > MAXCONTROLHEIGHT)
{    //I split my string by the carriage returns
    string[] options = { "\r\n" };
    string[] arr = Answer.Tag.ToString().Split(options, StringSplitOptions.None);

//Let's create the first text block to start displaying the list
    TextBlock b = new TextBlock();
    b.Foreground = DarkThemeIsVisible() //more on this function later
        ? new SolidColorBrush(Color.FromArgb(255, 255, 255, 255))
        : new SolidColorBrush(Color.FromArgb(255, 0, 0, 0));
    b.VerticalAlignment = System.Windows.VerticalAlignment.Top;
    b.FontSize = ANSWERTEXTFONTSIZE;
    b.TextWrapping = TextWrapping.Wrap;
    b.Width = 430;
    b.Name = "sb1"; //the name really does not matter

    int j = 0;
//This constant will help me omit the new line before the first line of text
    bool omitCRForNewLine = true;
//and let's display the text
    while (j < arr.Length)
    {
        if (!omitCRForNewLine)
            b.Text += Environment.NewLine + arr[j];
        else
            b.Text += arr[j];

        omitCRForNewLine = false;

        j += 1;
        if (b.ActualHeight > MAXCONTROLHEIGHT - 20)
        { //The height of the control is getting closer to the limit
//so let's add this control to the stack panel, and ...
            AnswerScrollViewerStackPanel.Children.Add(b);
//create a new control to continue displaying the text
            b = new TextBlock();
            b.Foreground = DarkThemeIsVisible()
                ? new SolidColorBrush(Color.FromArgb(255, 255, 255, 255))
                : new SolidColorBrush(Color.FromArgb(255, 0, 0, 0));
            b.VerticalAlignment = System.Windows.VerticalAlignment.Top;
            b.FontSize = ANSWERTEXTFONTSIZE;
            b.TextWrapping = TextWrapping.Wrap;
            b.Width = 430;
            b.Name = "sb" + j.ToString();
//reset this variable to avoid the empty line in the beginning
            omitCRForNewLine = true; 
        }

        if (j == arr.Length - 1) //add the last text block
            AnswerScrollViewerStackPanel.Children.Add(b);

    } //hide Answer text block, I am not using it
    Answer.Visibility = System.Windows.Visibility.Collapsed;
}
else
{ //if one text block is enough, everything is simple
    Answer.Visibility = System.Windows.Visibility.Visible;
    Answer.Text = Answer.Tag.ToString();

    Answer.Foreground = DarkThemeIsVisible()
        ? new SolidColorBrush(Color.FromArgb(255, 255, 255, 255))
        : new SolidColorBrush(Color.FromArgb(255, 0, 0, 0));
    Answer.VerticalAlignment = System.Windows.VerticalAlignment.Top;

    Answer.FontSize = ANSWERTEXTFONTSIZE;
}


And here are the two helper functions. The first function takes a control as input, and a line of text. Then it creates a TextBlock, assigns some parameters and returns the ActualHeight parameter of the control.

private double MeasureSizeOfATextBlock(TextBlock t, string text)
{
    TextBlock a = new TextBlock();
    a.Width = t.Width;
    a.FontFamily = t.FontFamily;
    a.FontStyle = t.FontStyle;
    a.FontWeight = t.FontWeight;
    a.FontSize = t.FontSize;
    a.Text = text;
    a.TextWrapping = t.TextWrapping;

    return a.ActualHeight;
}



This function determines whether the user has the dark or light theme selected.

private bool DarkThemeIsVisible()
{
    Visibility v = (Visibility)Resources["PhoneDarkThemeVisibility"];
    if (v == System.Windows.Visibility.Visible)
        return true;

    return false;
}




Friday, July 20, 2012

Windows Phone 7 ItemsControl within an ItemsControl, solving slow loading issue


So it happened that I needed to show a long list within a long list. I found that the scrolling performance of a ListBox was not up to par, so I tried an ItemsControl. Here is the XAML:


<ItemsControl 
    MaxWidth="450" VirtualizingStackPanel.VirtualizationMode="Recycling"
    ItemsSource="{Binding AnswersForDisplayAsAListBySomeNumberOfElements}"
    Visibility="{Binding ThisOneHasALongAnswer, Converter={StaticResource showLongAnswer}}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <<ItemsControl.Template>
        <ControlTemplate>
            <ScrollViewer x:Name="InnerScrollViewer" Padding="{TemplateBinding Padding}" MaxHeight="400">
                <ItemsPresenter />
            </ScrollViewer>
        </ControlTemplate>
    </ItemsControl.Template>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding}" TextWrapping="Wrap" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Upon binding, if the ItemsList is visible, it displays a scrollable list. The problem with this approach is that once the inner list is scrolled to the bottom (or to the top), the outer ItemsList is not being scrolled. After playing with it for a bit, I figured out that if the ScrollViewer is removed from the ItemsControl.Template part of the control declaration, then the long list is displayed in its entire height and scrolling the individual item scrolls the entire outer ItemsList

Sunday, July 15, 2012

Extract data from an app in Windows Phone 7


I needed to export some text data out of one Windows Phone 7 application. I did not care for the format, so I thought e-mailing data to myself would be just fine. Here is some code. First, let's build the data:

public StringBuilder GetData()
{
  StringBuilder result = new StringBuilder();

  List<InventoryItem> a = ReadInventoryItems().OrderBy(x=>x.SerialNumber).OrderBy(x => x.Account).Where(x => x.isFound == true).ToList<InventoryItem>();

  foreach (InventoryItem i in a)
  {
    LocatedItem l = new LocatedItem { 
        Account = i.Account, 
        SerialNo = i.SerialNumber, 
        wasFound = i.isFound, 
        withNote = i.Note 
    };
    result.Append(l.ToString() + Environment.NewLine);
  }
  return result;
}



InventoryItem and LocatedItem are just two classes that I use to store data:
public class InventoryItem
{
    public string SerialNumber { get; set; }
    public string Account { get; set; }
    public string ModelDesc { get; set; }
    public string Building { get; set; }
    public string Room { get; set; }
    public string BuildingRoom { get; set; }
    public bool isFound { get; set; }
    public string Note { get; set; }
}

 public class LocatedItem
 {
     public string Account {get; set;}
     public string SerialNo { get; set; }
     public bool wasFound { get; set; }
     public string withNote { get; set; }

     public override string ToString()
     {
         return String.Format("{0} {1} {2} {3}", Account, SerialNo, wasFound == true ? "1" : "0", withNote);
     }
 }


Data is ready, how to extract it? The help comes from EmailComposeTask class that resides is the Microsoft.Phone.Tasks namespace. Here is some code:
EmailComposeTask email = new EmailComposeTask();
emailcomposer.To = @"youraddress@yourmailserver.com";
emailcomposer.Subject = "test subject";
DataLayer dl = new DataLayer();
emailcomposer.Body = dl.GetAllFoundItems().ToString();
emailcomposer.Show();


And that is it! Once deployed to the phone, choose the e-mail provider that will send the message and send the e-mail.