简体   繁体   中英

Silverlight 4: How to make a combobox refresh its ItemsSource on open

I'd like to refresh the contents of a Silverlight 4 combobox on open.

This seems like a stupid-simple thing to want to do, yet I cannot for the life of me find a straight answer.

I have a form in a Silverlight 4 application with a combobox that lists Sales Order numbers. Many people will have this form open at the same time, so I'd like it to call back to the webservice ON OPEN and refresh its contents.

The closest thing I've found is this:
http://blogs.msdn.com/b/kylemc/archive/2010/06/18/combobox-sample-for-ria-services.aspx

..which I couldn't make work and doesn't help me anyway. There's nothing in that sample that allows me to re-populate the dropdown when it's opened.

I'm using MVVM and have used the Interactivity classes to pump the Open event of the combobox into my ViewModel. There, I call the webservice and reset the underlying property to which the comboboxes' ItemsSource is bound. Doesn't work- the dropdown flashes for a second, then opens, empty.

UPDATE:

XAML:

<ComboBox x:Name="cmbOrderNumber" Width="125" 
                      ItemsSource="{Binding ActiveSalesOrderNumbers, Mode=TwoWay}" 
                      IsEnabled="{Binding OrderSelectorEnabled}">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="SelectionChanged">
                        <inf:InvokeDelegateCommandAction Command="{Binding SalesOrderSelectedCommand}" CommandParameter="{Binding ElementName=cmbOrderNumber, Path=SelectedValue}"></inf:InvokeDelegateCommandAction>
                    </i:EventTrigger>
                    <i:EventTrigger EventName="DropDownOpened">
                        <inf:InvokeDelegateCommandAction Command="{Binding SalesOrderOpenedCommand}"></inf:InvokeDelegateCommandAction>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </ComboBox>

C#:

 public void OnSalesOrderOpenedCommand(object o)
        {
            _companyContext.Load(_companyContext.GetSales_Order_Numbers_FromDateQuery(_lastSalesOrderRequest), q => 
            {
                if (!q.HasError)
                {
                    q.Entities.ToList().ForEach(e => 
                    {
                        ActiveSalesOrderNumbers.Add(e.Sales_Order_Number);
                    });
                    _lastSalesOrderRequest = DateTime.Now;
                }
                else
                {
                    throw new Exception("Error updating sales order number list.");
                }
            }, null);
        }

The event gets triggered, and I've watched the data come back as expected from the service and add new items to the ActiveSalesOrderNumbers , which is an ObservableCollection. The dropdown DOES NOT update, no new records.

What does your list of items look like? If your ItemsSource is being bound to an ObservableCollection<T> on your ViewModel then you should be able to add/remove items from that collection and the ComboBox items will update properly. The ComboBox is notorious for really screwing with your bindings once it has been set once. I would suggest not trying to replace the entire ItemsSource but to use an ObservableCollection<T> , Clear() then Add(...) .

I'm working on a project right now that I have done this very thing on so I can verify that it works.

EDIT:

MainPage.xaml

<ComboBox
    x:Name="comboBox"
    ItemsSource="{Binding ActiveSalesOrderNumbers}"
    HorizontalAlignment="Center"
    Width="200"
    Height="27"
    Margin="30">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <StackPanel
                Orientation="Horizontal">
                <TextBlock>
                    <Run Text="{Binding SalesOrderNumber}"/>
                    <Run Text="{Binding LastModified}"/>
                </TextBlock>
            </StackPanel>
        </DataTemplate>
    </ComboBox.ItemTemplate>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="DropDownOpened">
            <ei:CallMethodAction MethodName="OnSalesOrderOpenedCommand" TargetObject="{Binding DataContext, ElementName=comboBox}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ComboBox>

MainPage.xaml.cs

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();

        this.DataContext = new MainPageViewModel();
    }
}

MainPageViewModel.cs

public class MainPageViewModel : NotifyObject
{
    private readonly SalesOrderRepository _repository;

    public MainPageViewModel()
        : this(new SalesOrderRepository())
    {
    }

    public MainPageViewModel(SalesOrderRepository repository)
    {
        _repository = repository;

        this.ActiveSalesOrderNumbers = new ObservableCollection<SalesOrder>();
    }

    private ObservableCollection<SalesOrder> _activeSalesOrderNumbers;
    public ObservableCollection<SalesOrder> ActiveSalesOrderNumbers
    {
        get { return _activeSalesOrderNumbers; }
        set
        {
            _activeSalesOrderNumbers = value;
            NotifyPropertyChanged(() => ActiveSalesOrderNumbers);
        }
    }

    public void OnSalesOrderOpenedCommand()
    {
        _repository.GetSalesOrderNumbers(result =>
        {
            this.ActiveSalesOrderNumbers.Clear();

            result.ToList().ForEach(e => { this.ActiveSalesOrderNumbers.Add(e); });
        });
    }
}

public class SalesOrder : NotifyObject
{
    private string _salesOrderNumber;
    public string SalesOrderNumber
    {
        get { return _salesOrderNumber; }
        set
        {
            _salesOrderNumber = value;
            NotifyPropertyChanged(() => SalesOrderNumber);
        }
    }

    private DateTime _lastModified;
    public DateTime LastModified
    {
        get { return _lastModified; }
        set
        {
            _lastModified = value;
            NotifyPropertyChanged(() => LastModified);
        }
    }
}

public class SalesOrderRepository
{
    public void GetSalesOrderNumbers(Action<IEnumerable<SalesOrder>> reply)
    {
        List<SalesOrder> orders = new List<SalesOrder>();

        for (int i = 0; i < 10; i++)
        {
            orders.Add(new SalesOrder { SalesOrderNumber = i.ToString(), LastModified = DateTime.Now });
        }

        reply(orders);
    }
}

NotifyObject.cs

public abstract class NotifyObject : INotifyPropertyChanged
{
    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    /// <summary>
    /// Raises this object's PropertyChanged event.
    /// </summary>
    /// <param name="propertyName">The property that has a new value.</param>
    protected void NotifyPropertyChanged(string propertyName)
    {
        this.VerifyPropertyName(propertyName);

        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);

            handler(this, e);
        }
    }

    /// <summary>
    /// Raises this object's PropertyChanged event.
    /// </summary>
    /// <typeparam name="TProperty">The type of the property.</typeparam>
    /// <param name="property">The property.</param>
    protected void NotifyPropertyChanged<TProperty>(Expression<Func<TProperty>> property)
    {
        var lambda = (LambdaExpression)property;

        MemberExpression memberExpression;

        if (lambda.Body is UnaryExpression)
        {
            var unaryExpression = (UnaryExpression)lambda.Body;
            memberExpression = (MemberExpression)unaryExpression.Operand;
        }
        else memberExpression = (MemberExpression)lambda.Body;

        NotifyPropertyChanged(memberExpression.Member.Name);
    }

    /// <summary>
    /// Warns the developer if this object does not have
    /// a public property with the specified name. This 
    /// method does not exist in a Release build.
    /// </summary>
    [Conditional("DEBUG")]
    public void VerifyPropertyName(string propertyName)
    {
        // If you raise PropertyChanged and do not specify a property name,
        // all properties on the object are considered to be changed by the binding system.
        if (String.IsNullOrEmpty(propertyName))
            return;

        // Verify that the property name matches a real,  
        // public, instance property on this object.
        if (this.GetType().GetProperties().Where(p => p.Name == propertyName).FirstOrDefault() == null)
        {
            throw new ArgumentException(String.Format("Invalid property name: {0}", propertyName));
        }
    }
}

TO be able to tie a source in the code-behind file you need to make that file a DepencyProperty for it to work, for instance like so:

    #region Title (DependenyProperty)
    public String Title
    {
        get { return (String)GetValue(TitleProperty); }
        set { SetValue(TitleProperty, value); }
    }
    public static readonly DependencyProperty TitleProperty =
        DependencyProperty.Register("Title", typeof(String), typeof(TopicListItem), new PropertyMetadata(null));
    #endregion String (DependenyProperty)

Atleast that is how I understand it works. Instead of a string you create the collection object you need to create. Tell us how it goes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM