uwp GridView Drop EventTriggerBehavior signature - xaml

I'm working on a Drag and Drop Xaml Uwp application with multiple ComboBox and GridView. I experimented with it a bit in xaml code behind until I thought I knew where I was heading with the app. I then began moving my logic into a ViewModel, PlayPageViewModel, I'm using MvvM Light and Template 10. I have many events working using interactions. I've had the Drop working in codeBehind when I move it over to the view model I get Cannot find method named GvNewPlayList_OnDrop on object of type TheLolFx.UWP.T10.ViewModels.PlayPageViewModel that matches the expected signature.
Exception
Exception {System.ArgumentException: Cannot find method named GvNewPlayList_OnDrop on object of type TheLolFx.UWP.T10.ViewModels.PlayPageViewModel that matches the expected signature.
at Microsoft.Xaml.Interactions.Core.CallMethodAction.Execute(Object sender, Object parameter)
at Microsoft.Xaml.Interactivity.Interaction.ExecuteActions(Object sender, ActionCollection actions, Object parameter)
at Microsoft.Xaml.Interactions.Core.EventTriggerBehavior.OnEvent(Object sender, Object eventArgs)} System.Exception {System.ArgumentException}
Message
Message "System.ArgumentException: Cannot find method named GvNewPlayList_OnDrop on object of type TheLolFx.UWP.T10.ViewModels.PlayPageViewModel that matches the expected signature.\r\n at Microsoft.Xaml.Interactions.Core.CallMethodAction.Execute(Object sender, Object parameter)\r\n at Microsoft.Xaml.Interactivity.Interaction.ExecuteActions(Object sender, ActionCollection actions, Object parameter)\r\n at Microsoft.Xaml.Interactions.Core.EventTriggerBehavior.OnEvent(Object sender, Object eventArgs)" string
I used the signature that was generated in the code behind. The ContainerContentChangingis fired in the ViewModel As you can see commented out I tried with just object too.
What is the correct signature?
XAML
<GridView x:Name="GvNewPlayList"
RelativePanel.Below="CbPlayListEditor"
Visibility="{Binding LbNewPlayListVisibility}"
Background="BurlyWood"
Padding="5"
Header="New Play List"
ItemsSource="{Binding NewLocalSoundsPlayListsSelectedItem.LocalSfxV2s}"
CanDragItems="True"
AllowDrop="True"
CanReorderItems="True"
IsItemClickEnabled="True"
DragItemsStarting="LbNewPlayList_OnDragItemsStarting"
DragOver="LbNewPlayList_OnDragOver">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Drop">
<core:CallMethodAction MethodName="GvNewPlayList_OnDrop"
TargetObject="{Binding Mode=OneWay}" />
</core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="ContainerContentChanging">
<core:CallMethodAction MethodName="GvLocalSoundsPlayListEditorContainerContentChangingAsync"
TargetObject="{Binding Mode=OneWay}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
<GridView.ItemTemplate>
<DataTemplate>
[...]
PlayPageViewModel
Exception fired on XAML attempting to target this method in the vm.
// private async void GvNewPlayList_OnDrop(object sender, object e)
// private async void GvNewPlayList_OnDrop()
private async void GvNewPlayList_OnDrop(object sender, DragEventArgs e)
{
e.AcceptedOperation = DataPackageOperation.Copy;
Logger.Log(this, "yup");
[...]
}
This one fires from the ContainerContentChanging event.
public async void GvLocalSoundsPlayListEditorContainerContentChangingAsync()
{
Logger.Log(this, $"GvLocalSoundsPlayListEditorContainerContentChangingAsync: {SelectedPlayList?.PlayListName}");
//_settings.CurrentPlayList = SelectedPlayList;
//LocalSounds = await _theLolFxV2DataServices.GetPlayListAsync(SelectedPlayList);
//NewLocalSoundsPlayListItems = await _theLolFxV2DataServices.GetPlayListAsync(CbPlayListEditorSelectedItem);
}

When using a CallMethodAction for invoking a method, the signature of the method should be like this: public void DoSomthing(). The reasons for the exception are:
GvNewPlayList_OnDrop is marked as private, it need mark as public;
It cannot contain any parameters.
So just modify its signature like that of the second method: public async void GvLocalSoundsPlayListEditorContainerContentChangingAsync()

Related

Incorrect Signature in XAML button clicked event

I am using Visual Studio 2017 and Resharper 2018.2. I am getting an "Incorrect Signature" error when resharper looks at my code.
<Button x:Name="ButtonNext"
BackgroundColor="#bebebe"
CornerRadius="5"
Grid.Column="1"
Grid.Row="0"
Text="Next"
TextColor="#FFFFFF"
Clicked="ButtonNextClickedAsync"></Button>
Signature is
private async System.Threading.Tasks.Task ButtonNextClickedAsync(object sender, EventArgs e)
It only gives this error when the method is async. Does anyone know what the correct signature is?
Try changing the signature to:
private async void ButtonNextClickedAsync(object sender, EventArgs e)
Then call your task inside this event handler.

Auto Repeat a MediaElement

I'm trying to auto repeat a MediaElement. I did that using the following code:
<MediaElement Name="MainMedia" MediaEnded="MainMedia_MediaEnded_1" />
private void MainMedia_MediaEnded_1(object sender, RoutedEventArgs e)
{
MainMedia.Position = TimeSpan.FromSeconds(0);
MainMedia.Play();
}
But I was wondering if there is any better solution.
There is a property called IsLooping. If you set that to true it will auto repeat the media.

Creating a generic, re-usable, Windows Phone 7 XAML form, and using it from a class library

Ok, so I'm newish to Windows Phone 7/Silverlight programming, and started what I thought would be a fairly straightfoward process, and have unfortunately run into a (hopefully!) small issue.
Basically, I'm trying to create a generic XAML form, e.g., an "About.xaml" form which is standard to all applications in my application suite. The idea is that this "About" screen looks the same, behaves the same, only difference being a few fields (e.g., application name etc) which are populated by the calling application. Plus, because it's shared, any new features/bug fixes/enhancements benefit all apps (i.e., re-use etc). My initial thoughts are that this XAML form should 'live' in a class library, which can be referenced by the various applications.
I've created a sample solution with two projects to highlight the problem.
First off, I create a Windows Phone Panorama Application, called it "WindowsPhonePanoramaApplication1". Next, I create a Windows Phone Class Library, which I call "WindowsPhoneClassLibrary1".
In "WindowsPhoneClassLibrary1", I create a new form class of type "Windows Phone Portrait Page", and call it "About.xaml".
To recreate the problem, I picked any event, e.g., the "SelectionChanged" event for the list box on the first page of the Panorama (any old event will do, just need a means of calling "NavigationService.Navigate(...))
<!--Panorama item one-->
<controls:PanoramaItem Header="first item">
<!--Double line list with text wrapping-->
<ListBox Margin="0,0,-12,0" ItemsSource="{Binding Items}" SelectionChanged="ListBox_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432">
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PanoramaItem>
In the code behind, I have the following code for the SelectionChanged event:
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
NavigationService.Navigate(new Uri("/AboutPage.xaml", UriKind.RelativeOrAbsolute));
}
When I run the application and click on any of the items in the listbox, the method RootFrame_NavigationFailed(object sender, NavigationFailedEventArgs e) is called, and the application stops at the Debugger.Break() line:
In the NavigationFailedEventArgs parameter, looking at the Exception object in there, the following error is shown:
{"No XAML was found at the location '/AboutPage.xaml'."}
[System.InvalidOperationException]: {"No XAML was found at the location '/AboutPage.xaml'."}
_data: null
_HResult: -2146233079
_innerException: null
_message: "No XAML was found at the location '/AboutPage.xaml'."
_methodDescs: {System.IntPtr[16]}
_optionalData: null
Data: {System.Collections.ListDictionaryInternal}
HResult: -2146233079
InnerException: Could not evaluate expression
Message: "No XAML was found at the location '/AboutPage.xaml'."
StackTrace: " at System.Windows.Navigation.PageResourceContentLoader.EndLoad(IAsyncResult asyncResult)\r\n at System.Windows.Navigation.NavigationService.ContentLoader_BeginLoad_Callback(IAsyncResult result)\r\n at System.Windows.Navigation.PageResourceContentLoader.BeginLoad_OnUIThread(AsyncCallback userCallback, PageResourceContentLoaderAsyncResult result)\r\n at System.Windows.Navigation.PageResourceContentLoader.<>c__DisplayClass4.<BeginLoad>b__0(Object args)\r\n at System.Reflection.RuntimeMethodInfo.InternalInvoke(RuntimeMethodInfo rtmi, Object obj, BindingFlags invokeAttr, Binder binder, Object parameters, CultureInfo culture, Boolean isBinderDefault, Assembly caller, Boolean verifyAccess, StackCrawlMark& stackMark)\r\n at System.Reflection.RuntimeMethodInfo.InternalInvoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, StackCrawlMark& stackMark)\r\n at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)\r\n at System.Del
egate.DynamicInvokeOne(Object[] args)\r\n at System.MulticastDelegate.DynamicInvokeImpl(Object[] args)\r\n at System.Delegate.DynamicInvoke(Object[] args)\r\n at System.Windows.Threading.DispatcherOperation.Invoke()\r\n at System.Windows.Threading.Dispatcher.Dispatch(DispatcherPriority priority)\r\n at System.Windows.Threading.Dispatcher.OnInvoke(Object context)\r\n at System.Windows.Hosting.CallbackCookie.Invoke(Object[] args)\r\n at System.Windows.Hosting.DelegateWrapper.InternalInvoke(Object[] args)\r\n at System.Windows.RuntimeHost.ManagedHost.InvokeDelegate(IntPtr pHandle, Int32 nParamCount, ScriptParam[] pParams, ScriptParam& pResult)\r\n"
I'm pretty certain the reason I get this error is because the "About.xaml" 'lives' in the class library "WindowsPhoneClassLibrary1", and not "WindowsPhonePanoramaApplication1" where the application is running from.
I have checked the XAP file that gets created for "WindowsPhonePanoramaApplication1", and sure enough it has the assembly "WindowsPhoneClassLibrary1.dll" contained within it. Also, I found a link on Jeff Prosise's blog, which highlights a way to navigate to a XAML form in an external assembly in Silverlight 4 (using the INavigationContentLoader interface), however Windows Phone 7 is based on Silverlight 3, and from searching the WP7 documentation, it doesn't appear to have that interface defined. I have had a browse of the URIMapping/URIMapper classes, but can't find anything obvious that would make the NavigationService look in the class library.
The question is, using Silverlight 3/Silverlight for Windows Phone 7, how do I 'tell' the "NavigationService" in "WindowsPhonePanoramaApplication1" to 'look in' the class library "WindowsPhoneClassLibrary1" for the "About.xaml" form? Surely, there must be some way of re-using XAML forms from a class library?
Also, if the above approach is simply the wrong way of going about achieving re-use of generic XAML forms, I'd be interested in any help/links that would point me in the right direction.
Thanks in advance for any help, it would be much appreciated...
Found the solution at this link, quite simple if you know the syntax :-)
In summary, the following worked for me:
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
NavigationService.Navigate(new Uri("/WindowsPhoneClassLibrary1;component/AboutPage.xaml", UriKind.Relative));
}

How do I detect waiting asynchronous wcf calls?

I make calls to a WCF service from my silverlight application.
I am doing this asynchronously and I am not blocking execution after making the async. call (that means, I am not using wait-join mechanism frm this page). I do not want the flow to get blocked.
However, I would like to detect that the WCF call has gone into a wait state so that I can show a busy icon on the UI - a visual communication indicating that things are happening behind the UI.
I can change my code such that I can start to animate the busy icon and stop that animation when the asynchronous call completes.
However, this is a lot of bolierplate code, and with more calls being made throughout the client code, this is only going to get messier.
So, is there any method or property exposed by the wcf service client reference code that can be used to trigger off events when any async wcf service calls go into a wait state, and likewise, trigger off events when the all async wcf service calls finish?
There is no property or event on the generated client reference class that can be used to identify that an asynchronous call to a method of a Silverlight WCF service is currently in progress. You can record this yourself using a simple boolean variable though, or by using the blocking thread synchronization that you mentioned you want to avoid in this case.
Here's an example of how to do what you want using the Silverlight ProgressBar control to indicate waiting/working on a call to a very simple Silverlight web service:
Page.xaml:
<UserControl x:Class="SilverlightApplication1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="100">
<StackPanel x:Name="LayoutRoot" Background="White">
<Button x:Name="ButtonDoWork" Content="Do Work"
Click="ButtonDoWork_Click"
Height="32" Width="100" Margin="0,20,0,0" />
<ProgressBar x:Name="ProgressBarWorking"
Height="10" Width="200" Margin="0,20,0,0" />
</StackPanel>
</UserControl>
Page.xaml.cs:
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using SilverlightApplication1.ServiceReference1;
namespace SilverlightApplication1
{
public partial class Page : UserControl
{
public bool IsWorking
{
get { return ProgressBarWorking.IsIndeterminate; }
set { ProgressBarWorking.IsIndeterminate = value; }
}
public Page()
{
InitializeComponent();
}
private void ButtonDoWork_Click(object sender, RoutedEventArgs e)
{
Service1Client client = new Service1Client();
client.DoWorkCompleted += OnClientDoWorkCompleted;
client.DoWorkAsync();
this.IsWorking = true;
}
private void OnClientDoWorkCompleted(object sender, AsyncCompletedEventArgs e)
{
this.IsWorking = false;
}
}
}
Setting IsIndeterminate to true after the asynchronous call to DoWork makes the progress bar animate indeterminately like this:
alt text http://www.freeimagehosting.net/uploads/89620987f0.png
Because the callback to OnClientDoWorkCompleted happens on the UI thread it's fine to change the value of the IsIndeterminate property back to false in the method body; this results in a non-animating blank ProgressBar again as the working/waiting is now finished.
Below is the code for the web service and the DoWork method that the above code calls asynchronously, all it does it simulate some long running task by sleeping for 5 seconds:
using System;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.Threading;
namespace SilverlightApplication1.Web
{
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class Service1
{
[OperationContract]
public void DoWork()
{
Thread.Sleep(TimeSpan.FromSeconds(5.0));
return;
}
}
}

Silverlight Combobox Databinding race condition

In my quest to develop a pretty data-driven silverlight app, I seem to continually come up against some sort of race condition that needs to be worked around. The latest one is below. Any help would be appreciated.
You have two tables on the back end: one is Components and one is Manufacturers. Every Component has ONE Manufacturer. Not at all an unusual, foreign key lookup-relationship.
I Silverlight, I access data via WCF service. I will make a call to Components_Get(id) to get the Current component (to view or edit) and a call to Manufacturers_GetAll() to get the complete list of manufacturers to populate the possible selections for a ComboBox. I then Bind the SelectedItem on the ComboBox to the Manufacturer for the Current Component and the ItemSource on the ComboBox to the list of possible Manufacturers. like this:
<UserControl.Resources>
<data:WebServiceDataManager x:Key="WebService" />
</UserControl.Resources>
<Grid DataContext={Binding Components.Current, mode=OneWay, Source={StaticResource WebService}}>
<ComboBox Grid.Row="2" Grid.Column="2" Style="{StaticResource ComboBoxStyle}" Margin="3"
ItemsSource="{Binding Manufacturers.All, Mode=OneWay, Source={StaticResource WebService}}"
SelectedItem="{Binding Manufacturer, Mode=TwoWay}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Name}" Style="{StaticResource DefaultTextStyle}"/>
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
This worked great for the longest time, until I got clever and did a little client side caching of the Component (which I planned turn on for the Manufacturers as well). When I turned on caching for the Component and I got a cache hit, all of the data would be there in the objects correctly, but the SelectedItem would fail to Bind. The reason for this, is that the calls are Asynchronous in Silverlight and with the benefit of the caching, the Component is not being returned prior to the Manufacturers. So when the SelectedItem tries to find the Components.Current.Manufacturer in the ItemsSource list, it is not there, because this list is still empty because Manufacturers.All has not loaded from the WCF service yet. Again, if I turn off the Component caching, it works again, but it feels WRONG - like I am just getting lucky that the timing is working out. The correct fix IMHO is for MS to fix the ComboBox/ ItemsControl control to understand that this WILL happen with Asynch calls being the norm. But until then, I need a need a way yo fix it...
Here are some options that I have thought of:
Eliminate the caching or turn it on across the board to once again mask the problem. Not Good IMHO, because this will fail again. Not really willing to sweep it back under the rug.
Create an intermediary object that would do the synchronization for me (that should be done in the ItemsControl itself). It would accept and Item and an ItemsList and then output and ItemWithItemsList property when both have a arrived. I would Bind the ComboBox to the resulting output so that it would never get one item before the other. My problem is that this seems like a pain but it will make certain that the race condition does not re-occur.
Any thougnts/Comments?
FWIW: I will post my solution here for the benefit of others.
#Joe: Thanks so much for the response. I am aware of the need to update the UI only from the UI thread. It is my understanding and I think I have confirmed this through the debugger that in SL2, that the code generated by the the Service Reference takes care of this for you. i.e. when I call Manufacturers_GetAll_Asynch(), I get the Result through the Manufacturers_GetAll_Completed event. If you look inside the Service Reference code that is generated, it ensures that the *Completed event handler is called from the UI thread. My problem is not this, it is that I make two different calls (one for the manufacturers list and one for the component that references an id of a manufacturer) and then Bind both of these results to a single ComboBox. They both Bind on the UI thread, the problem is that if the list does not get there before the selection, the selection is ignored.
Also note that this is still a problem if you just set the ItemSource and the SelectedItem in the wrong order!!!
Another Update:
While there is still the combobox race condition, I discovered something else interesting. You should NEVER genrate a PropertyChanged event from within the "getter" for that property. Example: in my SL data object of type ManufacturerData, I have a property called "All". In the Get{} it checks to see if it has been loaded, if not it loads it like this:
public class ManufacturersData : DataServiceAccessbase
{
public ObservableCollection<Web.Manufacturer> All
{
get
{
if (!AllLoaded)
LoadAllManufacturersAsync();
return mAll;
}
private set
{
mAll = value;
OnPropertyChanged("All");
}
}
private void LoadAllManufacturersAsync()
{
if (!mCurrentlyLoadingAll)
{
mCurrentlyLoadingAll = true;
// check to see if this component is loaded in local Isolated Storage, if not get it from the webservice
ObservableCollection<Web.Manufacturer> all = IsoStorageManager.GetDataTransferObjectFromCache<ObservableCollection<Web.Manufacturer>>(mAllManufacturersIsoStoreFilename);
if (null != all)
{
UpdateAll(all);
mCurrentlyLoadingAll = false;
}
else
{
Web.SystemBuilderClient sbc = GetSystemBuilderClient();
sbc.Manufacturers_GetAllCompleted += new EventHandler<hookitupright.com.silverlight.data.Web.Manufacturers_GetAllCompletedEventArgs>(sbc_Manufacturers_GetAllCompleted);
sbc.Manufacturers_GetAllAsync(); ;
}
}
}
private void UpdateAll(ObservableCollection<Web.Manufacturer> all)
{
All = all;
AllLoaded = true;
}
private void sbc_Manufacturers_GetAllCompleted(object sender, hookitupright.com.silverlight.data.Web.Manufacturers_GetAllCompletedEventArgs e)
{
if (e.Error == null)
{
UpdateAll(e.Result.Records);
IsoStorageManager.CacheDataTransferObject<ObservableCollection<Web.Manufacturer>>(e.Result.Records, mAllManufacturersIsoStoreFilename);
}
else
OnWebServiceError(e.Error);
mCurrentlyLoadingAll = false;
}
}
Note that this code FAILS on a "cache hit" because it will generate an PropertyChanged event for "All" from within the All { Get {}} method which would normally cause the Binding System to call All {get{}} again...I copied this pattern of creating bindable silverlight data objects from a ScottGu blog posting way back and it has served me well overall, but stuff like this makes it pretty tricky. Luckily the fix is simple. Hope this helps someone else.
Ok I have found the answer (using a lot of Reflector to figure out how the ComboBox works).
The problem exists when the ItemSource is set after the SelectedItem is set. When this happens the Combobx sees it as a complete Reset of the selection and clears the SelectedItem/SelectedIndex. You can see this here in the System.Windows.Controls.Primitives.Selector (the base class for the ComboBox):
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
int selectedIndex = this.SelectedIndex;
bool flag = this.IsInit && this._initializingData.IsIndexSet;
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
if (!this.AddedWithSelectionSet(e.NewStartingIndex, e.NewStartingIndex + e.NewItems.Count))
{
if ((e.NewStartingIndex <= selectedIndex) && !flag)
{
this._processingSelectionPropertyChange = true;
this.SelectedIndex += e.NewItems.Count;
this._processingSelectionPropertyChange = false;
}
if (e.NewStartingIndex > this._focusedIndex)
{
return;
}
this.SetFocusedItem(this._focusedIndex + e.NewItems.Count, false);
}
return;
case NotifyCollectionChangedAction.Remove:
if (((e.OldStartingIndex > selectedIndex) || (selectedIndex >= (e.OldStartingIndex + e.OldItems.Count))) && (e.OldStartingIndex < selectedIndex))
{
this._processingSelectionPropertyChange = true;
this.SelectedIndex -= e.OldItems.Count;
this._processingSelectionPropertyChange = false;
}
if ((e.OldStartingIndex <= this._focusedIndex) && (this._focusedIndex < (e.OldStartingIndex + e.OldItems.Count)))
{
this.SetFocusedItem(-1, false);
return;
}
if (e.OldStartingIndex < selectedIndex)
{
this.SetFocusedItem(this._focusedIndex - e.OldItems.Count, false);
}
return;
case NotifyCollectionChangedAction.Replace:
if (!this.AddedWithSelectionSet(e.NewStartingIndex, e.NewStartingIndex + e.NewItems.Count))
{
if ((e.OldStartingIndex <= selectedIndex) && (selectedIndex < (e.OldStartingIndex + e.OldItems.Count)))
{
this.SelectedIndex = -1;
}
if ((e.OldStartingIndex > this._focusedIndex) || (this._focusedIndex >= (e.OldStartingIndex + e.OldItems.Count)))
{
return;
}
this.SetFocusedItem(-1, false);
}
return;
case NotifyCollectionChangedAction.Reset:
if (!this.AddedWithSelectionSet(0, base.Items.Count) && !flag)
{
this.SelectedIndex = -1;
this.SetFocusedItem(-1, false);
}
return;
}
throw new InvalidOperationException();
}
Note the last case - the reset...When you load a new ItemSource you end up here and any SelectedItem/SelectedIndex gets blown away?!?!
Well the solution was pretty simple in the end. i just subclassed the errant ComboBox and provided and override for this method as follows. Though I did have to add a :
public class FixedComboBox : ComboBox
{
public FixedComboBox()
: base()
{
// This is here to sync the dep properties (OnSelectedItemChanged is private is the base class - thanks M$)
base.SelectionChanged += (s, e) => { FixedSelectedItem = SelectedItem; };
}
// need to add a safe dependency property here to bind to - this will store off the "requested selectedItem"
// this whole this is a kludgy wrapper because the OnSelectedItemChanged is private in the base class
public readonly static DependencyProperty FixedSelectedItemProperty = DependencyProperty.Register("FixedSelectedItem", typeof(object), typeof(FixedComboBox), new PropertyMetadata(null, new PropertyChangedCallback(FixedSelectedItemPropertyChanged)));
private static void FixedSelectedItemPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
FixedComboBox fcb = obj as FixedComboBox;
fcb.mLastSelection = e.NewValue;
fcb.SelectedItem = e.NewValue;
}
public object FixedSelectedItem
{
get { return GetValue(FixedSelectedItemProperty); }
set { SetValue(FixedSelectedItemProperty, value);}
}
protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (-1 == SelectedIndex)
{
// if after the base class is called, there is no selection, try
if (null != mLastSelection && Items.Contains(mLastSelection))
SelectedItem = mLastSelection;
}
}
protected object mLastSelection = null;
}
All that this does is (a) save off the old SelectedItem and then (b) check that if after the ItemsChanged, if we have no selection made and the old SelectedItem exists in the new list...well...Selected It!
I was incensed when I first ran into this problem, but I figured there had to be a way around it. My best effort so far is detailed in the post.
Link
I was pretty happy as it narrowed the syntax to something like the following.
<ComboBox Name="AComboBox"
ItemsSource="{Binding Data, ElementName=ASource}"
SelectedItem="{Binding A, Mode=TwoWay}"
ex:ComboBox.Mode="Async" />
Kyle
I struggled through this same issue while building cascading comboboxes, and stumbled across a blog post of someone who found an easy but surprising fix. Call UpdateLayout() after setting the .ItemsSource but before setting the SelectedItem. This must force the code to block until the databinding is complete. I'm not exactly sure why it fixes it but I've not experienced the race condition again since...
Source of this info: http://compiledexperience.com/Blog/post/Gotcha-when-databinding-a-ComboBox-in-Silverlight.aspx
It is not clear from your post whether you are aware that you must modify UI elements on the UI thread - or you will have problems. Here is a brief example which creates a background thread which modifies a TextBox with the current time.
The key is MyTextBox.Dispather.BeginInvoke in Page.xaml.cs.
Page.xaml:
<UserControl x:Class="App.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300"
Loaded="UserControl_Loaded">
<Grid x:Name="LayoutRoot">
<TextBox FontSize="36" Text="Just getting started." x:Name="MyTextBox">
</TextBox>
</Grid>
</UserControl>
Page.xaml.cs:
using System;
using System.Windows;
using System.Windows.Controls;
namespace App
{
public partial class Page : UserControl
{
public Page()
{
InitializeComponent();
}
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
// Create our own thread because it runs forever.
new System.Threading.Thread(new System.Threading.ThreadStart(RunForever)).Start();
}
void RunForever()
{
System.Random rand = new Random();
while (true)
{
// We want to get the text on the background thread. The idea
// is to do as much work as possible on the background thread
// so that we do as little work as possible on the UI thread.
// Obviously this matters more for accessing a web service or
// database or doing complex computations - we do this to make
// the point.
var now = System.DateTime.Now;
string text = string.Format("{0}.{1}.{2}.{3}", now.Hour, now.Minute, now.Second, now.Millisecond);
// We must dispatch this work to the UI thread. If we try to
// set MyTextBox.Text from this background thread, an exception
// will be thrown.
MyTextBox.Dispatcher.BeginInvoke(delegate()
{
// This code is executed asynchronously on the
// Silverlight UI Thread.
MyTextBox.Text = text;
});
//
// This code is running on the background thread. If we executed this
// code on the UI thread, the UI would be unresponsive.
//
// Sleep between 0 and 2500 millisends.
System.Threading.Thread.Sleep(rand.Next(2500));
}
}
}
}
So, if you want to get things asynchronously, you will have to use Control.Dispatcher.BeginInvoke to notify the UI element that you have some new data.
Rather than rebinding the ItemsSource each time it would have been easier for you to bind it to an ObservableCollection<> and then call Clear() on it and Add(...) all elements. This way the binding isn't reset.
Another gotcha is that the selected item MUST be an instance of the objects in the list. I made a mistake once when I thought the queried list for the default item was fixed but was regenerated on each call. Thus the current was different though it had a DisplayPath property that was the same as an item of the list.
You could still get the current item's ID (or whatever uniquely defines it), rebind the control and then find in the bound list the item with the same ID and bind that item as the current.
In case you arrive here because you have a Combobox selection problem, meaning, nothing happens when you click on your item in the list. Note that the following hints might also help you:
1/ make sure you do not notify something in case you select an item
public string SelectedItem
{
get
{
return this.selectedItem;
}
set
{
if (this.selectedItem != value)
{
this.selectedItem = value;
//this.OnPropertyChanged("SelectedItem");
}
}
}
2/ make sure the item you select is still in the underlying datasource in case you delete it by accident
I made both errors ;)