开发者

ListBox not populated in WP7 after values return from web request

I am trying to build a simple application for WP7 from scratch. The List Box in MainPage.xaml looks like this:

<Grid x:Name="ContentPanel" Grid.Row="1" >
    <ListBox x:Name="MainListBox" ItemsSource="{Binding result}" SelectionChanged="MainListBox_SelectionChanged">
        <ListBox.ItemTemplate>
           <DataTemplate>
               <StackPanel>
                   <TextBlock Text="{Binding name}" TextWrapping="Wrap" />
                   <TextBlock Text="{Binding description}" TextWrapping="Wrap" />
               </StackPanel>
           </DataTemplate>
        </ListBox.ItemTemplate>
   </ListBox>
</Grid>

In order to populate result and its elements, name and description, I need to fetch the values from a web server that responds with json for the get request I send. Since I need to make different calls to the server that respond with different json objects, I am maintaining different .cs files for each, independant of mainpage.cs (where i only initialize, define MainPage_Loaded and MainListBox_SelectionChanged). All the data is fetched and processed in the individual .cs files. Now the problem is that when I make the httpwebrequest and the reponse is retrieved, the UI is loaded well before that. As I understand from other posts the BeginGetResponse becomes a background process and the UI is loaded to keep it reponsive. So to summarize before the web request is returned with the data from the server, the UI is loaded as blank since the data is not populated. Now I cannot use the Dispatcher to populate since I am not in MainPage.cs and so can't access the List Box directly. I tried callback also but with no success. Please help

MainPage.cs:

public partial class MainPage : PhoneApplicationPage
{
    // Constructor
    public MainPage()
    {
        InitializeComponent();
        this.Loaded +=new RoutedEventHandler(MainPage_Loaded);
    }

    private void MainListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (MainListBox.SelectedIndex == -1)
            return;

        NavigationService.Navigate(new Uri("/DetailsPage.xaml?selectedItem=" + MainListBox.SelectedIndex, UriKind.Relative));

        MainListBox.SelectedIndex = -1;
    }

    // Load data for the ViewModel Items
    private void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        if (!App.ViewModel.IsDataLoaded)            
            App.ViewModel.LoadData();            
    }
}

LoadData() essentially initializes the request parameters, prepares URI and send the HTTP request to another function in another class (executeRequest.cs file) that gets the reponse and processes the request into objects that I could map to:

public void decodeJson<E>(HttpWebRequest request)
    {
        request.Method = "GET";
        var reqresult = (IAsyncResult)request.BeginGetResponse(ResponseCallback<E>, request);
    }

    public void ResponseCallback<E>(IAsyncResult reqresult)
    {
        var request = (HttpWebRequest)reqresult.AsyncState;
        var response = (HttpWebResponse)request.EndGetResponse(reqresult);

        if (response.StatusCode == HttpStatusCode.OK)
        {
            var stream = response.GetResponseStream();
            var reader = new StreamReader(stream);
            var contents = reader.ReadToEnd();
            if (contents.ToString().StartsWith("{\"jsonrpc"))
            {
                using (var ms = new MemoryStream(Encoding.Unicode.GetBytes(contents)))
                {
                    Type typeofe = typeof(E);
                    var deserializer = new DataContractJsonSerializer(typeof(E));
                    E element = (E)deserializer.ReadObject(ms);
                    populateResult<E>(element);
                }
            }
        }
    }

In populateResult I am trying to populate the UI. But by the time control enters BeginGetResponse my UI is populated and in populateResult since the List Box, MainListBox is not accessible I cannot use the dispatcher to refresh the UI with the new data

To give more info, result is an ObservableCollection of a class that contains different properties that come in from the json

The populateResult is very simple:

private void processResult<E>(E element)
    {
        //Checking for the type of result so that I can map
        string typeis = typeof(E).ToString();
        if (typeis.EndsWith("MyViewModel")
            {
              App.ViewModel.result = (element as MyViewModel).result;
              ???
            }
    }

Probably I should admit this (???) is where I am stuck. The result collection is updated but not in the UI. The UI is still blank and I cannot access the MainListBox from populateRsult to update. Hope it is clear. Else please tell me

With due courtesy let me also provide with MyViewModel and the SubViewModel

MyViewModel:

public class MyViewModel : INotifyPropertyChanged
{
    public MyViewModel()
    {            
        this.result = new ObservableCollection<SubViewModel>();
    }        
    public ObservableCollection<SubViewModel> result { get; set; }
    private string _sampleProperty = "Sample Runtime Property Value";
    public string SampleProperty
    {
        get
        {
            return _sampleProperty;
        }
        set
        {
            _sampleProperty = value;
            NotifyPropertyChanged("SampleProperty");
        }
    }

    public bool IsDataLoaded
    {
        get;
        private set;
    }
    public void LoadData()
    {
        //Initialize all the parameters
        HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(string.Format
            (uri with parameters);            
        request.Method = "GET";
        //call the decide json to get the reponse and parse it
        executeRequest execReq = new executeRequest();
        execReq.decodeJson<MyViewModel>(request);

        this.IsDataLoaded = true;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(String propertyName)
    {
        if (null !开发者_如何学编程= PropertyChanged)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

SubviewModel:

public class SubViewModel : INotifyPropertyChanged 
{
    private string _name;
    public string name
    {
        get
        {
            return _name;
        }
        set
        {
            _name = value;
            NotifyPropertyChanged("name");
        }
    }

    private string _description;
    public string description
    {
        get
        {
            return _description;
        }
        set
        {
            _description = value;
            NotifyPropertyChanged("description");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(String propertyName) 
    {
        if (null != PropertyChanged) 
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}


I don't see anything about how you're binding what to your listbox. you have a binding to result in your xaml, but aren't showing the declaration of what it actually is. If it is an observable collection, your populateResult method should be pretty trivial, just updating the observable collection's contents with new data.

If you are re-creating the observable collection in populate result, instead of just changing its content, that could be your problem.

update:

The biggest issue (from what i can tell from your updated code) is that your view model isn't firing a property change when you set result. so you need to do one of two things:

a) add property change support on result like the view model class has for SampleProperty, so that when it gets modified, bindings see the change:

private ObservableCollection<SubViewModel> _result = new ObservableCollection<SubViewModel>();
public ObservableCollection Result    
{
    get
    {
        return _result;
    }        
    set
    {
        _result = value;
        NotifyPropertyChanged("Result");
    }
}

// -- elsewhere --
private void processResult<E>(E element)    
{        
    // (why were you using the type **name** to check the 
    // type of the result instead of just using `as`?)
    var model = element as MyViewModel;
    if (model != null)
    {              
        App.ViewModel.Result = model.result;              
    }    
}

b) or you need to not set result, but modify its contents since it is an observable collection already:

private void processResult<E>(E element)    
{        
    var model = element as MyViewModel;
    if (model != null)
    {              
        // result is an ObservableCollection, so just modify its contents.  
        // anything bound to it will see the changes via collection change notifications
        App.ViewModel.result.ClearItems();
        foreach (var x in model.result)
            App.ViewModel.result.Add(x);
    }    
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜