Thursday, April 24, 2014

How To - Default Text on WPF Combo Boxes

When you want to add a default text (i.e. Please Select/ Select Item etc.) to a ComboBox in WPF, there is a easier way to do it. Take a look at the code below.

 <ComboBox Name="comboBox1"            
          Text="--Select Item--"
          IsEditable="true"
          IsReadOnly="true"/> 
This works. You get a nice "--Select Item--" on the combo box but the problem is, the ComboBox is editable. I don't want this behavior. I want the default text to appear in an uneditable combo box. Like this,


StackOverFlow is our friend. There is a much better answer using a Combo Box Behavior. But in this code, there is a small problem.

 public static readonly DependencyProperty DefaultTextProperty =
        DependencyProperty.RegisterAttached("DefaultText", typeof(String), typeof(ComboBox), new PropertyMetadata(null));
Note the ComboBox in 3rd parameter. This causes an issue when used in more than one combo box. 'DefaultText' Property already registered by 'ComboBox'. 

So the fix would be to mention ComboBoxBehaviors as the 3rd Parameter and attach a method to hookup the combobox behavior to any combo box which is using our Default Text behavior.

public static class ComboBoxBehaviors
    {
        public static readonly DependencyProperty DefaultTextProperty =
            DependencyProperty.RegisterAttached("DefaultText", 
                  typeof(String), 
                  typeof(ComboBoxBehaviors), 
                  new PropertyMetadata(null, HookupBehavior));

        public static String GetDefaultText(DependencyObject obj)
        {
            return (String)obj.GetValue(DefaultTextProperty);
        }

        private static void HookupBehavior(DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            ComboBox combo = d as ComboBox;
            if (combo == null) return;

            SetDefaultText(d, e.NewValue.ToString());
        }

        public static void SetDefaultText(DependencyObject obj, String value)
        {
            var combo = (ComboBox)obj;

            RefreshDefaultText(combo, value);

            combo.SelectionChanged += (sender, _) => RefreshDefaultText((ComboBox)sender, GetDefaultText((ComboBox)sender));

            obj.SetValue(DefaultTextProperty, value);
        }

        static void RefreshDefaultText(ComboBox combo, string text)
        {
            // if item is selected and DefaultText is set
            if (combo.SelectedIndex == -1 && !String.IsNullOrEmpty(text))
            {
                // Show DefaultText
                var visual = new TextBlock()
                {
                    FontStyle = FontStyles.Italic,
                    Text = text,
                    Foreground = Brushes.Gray
                };

                combo.Background = new VisualBrush(visual)
                {
                    Stretch = Stretch.None,
                    AlignmentX = AlignmentX.Left,
                    AlignmentY = AlignmentY.Center,
                    Transform = new TranslateTransform(3, 0)
                };
            }
            else
            {
                // Hide DefaultText
                combo.Background = null;
            }
        }
    }

You can use it like this:

<UserControl x:Class="Samples.WPF.MyView" 
             ....

xmlns:behaviors="clr-namespace:Samples.WPF.Behaviors"

.... d:DesignHeight="300" d:DesignWidth="300"> <ComboBox Name="BillCyclesComboBox" ItemsSource="{Binding BillCycles}"

behaviors:ComboBoxBehaviors.DefaultText="-- Please Select --"

DisplayMemberPath="Description" SelectedValuePath="ID" SelectedItem="{Binding Path=SelectedBillCycle}" /> ... </UserControl>

Tuesday, April 8, 2014

Non repeating random numbers in Java (Android)

Recently on a app I was working on, I had to select an element from either of two lists randomly. I tried using the normal new Random().nextInt( arrayLength - 1 ); but id didn't work on short lists.

Say a given length of 4, I'm more interested in getting an output like 4, 1, 3, 2, 1, 4, 2, 3, 4 but instead what I got was 1, 1, 1, 1, 2, 3, 2, 2, 4, 3, 2, 1, 1.

So as usual, SO to the rescue. How can I generate a random number within a range but exclude some?

This code, can do magic.

public int getRandomWithExclusion(Random rnd, int start, int end, int... exclude) {
    int random = start + rnd.nextInt(end - start + 1 - exclude.length);
    for (int ex : exclude) {
        if (random < ex) {
            break;
        }
        random++;
    }
    return random;
}

I did a few changes to better suit to my needs and following is the code which I'm using to access this method. There I have 2 variables to store the previous index from either of the lists.

int _prevArrayId = 0, _prevDBId = 0;

public void doSomething(){
    boolean arrayOrDB = rand.nextInt() % 10 >= 5;

    int id;

    if (arrayOrDB) {
        int len = Utils.SomeArray.length;

        // Array indices start with 0 and ends with (length - 1)
        id = Utils.getRandomWithExclusion(rand, 0, len, len,_prevArrayId);

        _prevArrayId = id;

        // Do stuff!
    } else { 

        // Database table rows start with 1 and ends in MaxID (count)
        id = Utils.getRandomWithExclusion(rand, 1, count,_prevDBId);

        _prevDBId = id;

        SomeItemFromDB item = ContentProviderHelpers.getItem(contentResolver, id);
        // Do stuff!
    }
}