Win8 ComboBox IsDropDownOpen + Visibility causes Glitched UI - c#

I am developing a Windows 8 App using XAML and C#.
I have a problem with my ComboBox, and have a simple example to demonstrate it.
Add the following to a Layout Aware Page (New BasicPage)
<ComboBox x:Name="comboBox1" DropDownClosed="comboBox1_DropDownClosed" Visibility="Collapsed" HorizontalAlignment="Left" Margin="179,217,0,0" Grid.Row="1" VerticalAlignment="Top" Width="998" Height="51">
<x:String>Option 1</x:String>
<x:String>Option 2</x:String>
<x:String>Option 3</x:String>
</ComboBox>
<Button Click="Button_Click" Margin="585,130,0,416" Grid.Row="1" Height="82" Width="154">
<Viewbox>
<TextBlock Text="Press Me" />
</Viewbox>
</Button>
Add this to the page's CodeBehind
private void Button_Click(object sender, RoutedEventArgs e)
{
comboBox1.Visibility = Windows.UI.Xaml.Visibility.Visible;
comboBox1.IsDropDownOpen = true;
}
private void comboBox1_DropDownClosed(object sender, object e)
{
comboBox1.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
Expected:
When the button is pressed, the ComboBox should appear and the options should expand, allowing the user to select one. Once the user has selected an option, the ComboBox disappears.
Actual Result:
When the button is pressed, nothing happens. If the button is pressed a second time, the ComboBox appears in a glitched state, and the app is essentially non-responsive. (All input is directed at the ComboBox, which never closes.
Note: The DropDownClosed event fires immediately after the Button_Click event does. Removing the event handler doesn't change anything, but it's interesting that the DropDownClosed event is firing.
Rejected Solution:
It was suggested to me to use Dispatcher.RunAsync to set IsDropDownOpen after the Visibility change has taken effect. This seems to be a race condition, because it only works some of the time. If there were a way to confirm that the ComboBox had been rendered visible, adding this check to the RunAsync method could solve the problem.
As a workaround, I'm currently delaying Dispatcher.RunAsync for 200 milliseconds, which is an annoying workaround. Any other ideas?

You're right, you need to make sure comboBox1 is actually rendered visible, before trying to set IsDropDownOpen. The way to do it is to make the second call via Dispatcher:
private void Button_Click(object sender, RoutedEventArgs e)
{
comboBox1.Visibility = Windows.UI.Xaml.Visibility.Visible;
Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => comboBox1.IsDropDownOpen = true);
}

What a nasty bug, ah?
A simple workaround is instead of using the Visibility property, use Opacity. It works as expected:
<ComboBox x:Name="comboBox1" DropDownClosed="comboBox1_DropDownClosed" Opacity="0" HorizontalAlignment="Left" Margin="179,217,0,0" Grid.Row="1" VerticalAlignment="Top" Width="998" Height="51">
<x:String>Option 1</x:String>
<x:String>Option 2</x:String>
<x:String>Option 3</x:String>
</ComboBox>
private void Button_Click(object sender, RoutedEventArgs e) {
comboBox1.Opacity = 1;
comboBox1.IsDropDownOpen = true;
}
private void comboBox1_DropDownClosed(object sender, object e) {
comboBox1.Opacity = 0;
}
Cheers!

I've tested the following on my desktop and Surface device, and it seems to work all the time. It is a variation on delaying setting IsDropDownOpen. I understand you may have tried some variation of this that produced a race condition. I am not seeing a race condtion, so hopefully it works for you as well.
// need this for Task
using System.Threading.Tasks;
...
// note async keyword added to function signature
async private void Button_Click(object sender, RoutedEventArgs e)
{
comboBox1.Visibility = Windows.UI.Xaml.Visibility.Visible;
// add small delay before opening dropdown
await Task.Delay(1);
comboBox1.IsDropDownOpen = true;
}

Related

How to allow pressing only the cancel button when the value is invalid?

I have a simple dialog with a SpinEdit and two buttons: OK_Button and Cancel_Button. I've set a mask for the value in the SpinEdit and the dialog won't let me press the cancel button when the value is invalid. I've tried changing the SpinEdit's property to InvalidValueBehavior="AllowLeaveEditor" but then I can click both, OK and cancel button. Is there a way to ONLY allow pressing cancel when the value is incorrect?
XAML:
<dxe:SpinEdit x:Name="dxSpinEdit"
Height="23" MinWidth="200" Width="Auto"
HorizontalAlignment="Right"
Text="{Binding Value, Mode=TwoWay}"
MaskType="Numeric"
IsFloatValue="{Binding FloatValue}"
MinValue="{Binding MinValue}"
MaxValue="{Binding MaxValue}"
Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}"
MaskShowPlaceHolders="{Binding ShowPlaceHolder}"
InvalidValueBehavior="WaitForValidValue"
/>
<StackPanel Grid.Row="1" x:Uid="OKCancel_Buttons" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<Button Height="23" x:Name="OK_Button" Click="OK_Click" Content="OK" IsDefault="True" HorizontalAlignment="Right" MinWidth="95" />
<Button Height="23" x:Name="Cancel_Button" Click="Cancel_Click" Content="Cancel" HorizontalAlignment="Right" MinWidth="95" PreviewMouseDown="win_PreviewMouseDown" />
</StackPanel>
I looked up this issue on the devexpress forum but their solution didn't work for me. I've implemented the MouseDownPreview event like so:
C# (code behind)
private void OK_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
private void Cancel_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
private void win_PreviewMouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if(e.Source == Cancel_Button)
{
DialogResult = false;
Close();
}
}
But the event wasn't handled at all. I'd like to keep the property InvalidValueBehavior at the value "WaitForValidValue" but at the same time I'd like to allow pressing the Cancel button.
Even if you're not going to go the full MVVM route, you should switch from using click events to an ICommand implementation that supports CanExecute logic (such as this one from MVVM Light).
Using a command will automatically disable any bound control (e.g. button or menu item) when CanExecute is false. You can then have all the logic for controlling your commands grouped in one place, including validation that will only allow OK to be clicked when your object is in a valid state.
If you just want to go the standard WPF (non MVVM) route, you could add something like this in your window's constructor
public MyView()
{
....
Ok_Button.Command =
new RelayCommand(() => DialogResult = true, // just setting DialogResult is sufficient, no need to call Close()
// put the required validation logic here
() => dxSpinEdit.Value > 0 && dxSpinEdit.Value < 10);
Cancel_Button.Command = new RelayCommand(() => DialogResult = false);
// replace this with the actual event from SpinEdit
dxSpinEdit.ValueChanged += (s,e) => (OK_Button.Command as RelayCommand).RaiseCanExecuteChanged();
}
Yes I know it looks ugly 😀 - I'd suggest following the MVVM design pattern instead. When using MVVM, all of the command functionality belongs in your ViewModel.
Either way, you can then remove all the click and mousedown handlers from your buttons.

UWP Get Clicked Item in DataTemplate

I'm sure this has been asked before, however I cannot find the appropriate answer.
I am using an ItemTemplate to display a list of users, this list consists of StackPanels with user details. How can I get the user object that I clicked on?
Current code:
<GridView Name="usersList">
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel PointerPressed="UserClicked">
<TextBlock Text="{Binding Forename}"/>
<TextBlock Text="{Binding Surname}"/>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
This is being populated via an async API call:
usersList.ItemsSource = null;
var task = Users.UserAsync<User>();
usersList.ItemsSource = await task;
How can I capture the User object that has been clicked on PointerPressed?
Like this:
private void UserClicked(object sender, PointerRoutedEventArgs e)
{
User clickedOnUser = (sender as StackPanel).DataContext as User;
}
This approach is more direct than the one using usersList.SelectedItem, so I recommend sticking with it. E.g., it will work if you set SelectionMode="None" for the GridView, whereas the approach based on usersList.SelectedItem won't.
Also, as Sean O'Neil rightfully noticed, you might want to use Tapped event instead of PointerPressed for the most general case scenario.
Use GridView.SelectedItem to reference the object you want when you click on it.
private void UserClicked_PointerPressed(object sender, PointerRoutedEventArgs e)
{
var whatYouWant = usersList.SelectedItem;
}

Wpf UI bahaviour strangeness in ContinueWith, when Task itself works too fast

Code to emulate bug.
XAML:
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Grid.Column="0"
Margin="5,5,5,0"
FontSize="14"
HorizontalAlignment="Right"
Text="User:">
</TextBlock>
<TextBox Grid.Row="0"
Grid.Column="1"
Margin="5,5,5,0"
Name="_txtLogin"
Text="{Binding Login}"/>
<Button Grid.Row="1"
Grid.Column="1"
Content="Ok"
Width="100"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Click="AutentificateClick"
Padding="0,2,0,2"
IsDefault="True"
Name="_btnOk"
/>
</Grid>
Code:
private Boolean _isFirstTime = true;
public Window2()
{
InitializeComponent();
DataContext = this;
}
private void AutentificateClick(Object sender, RoutedEventArgs e)
{
_btnOk.IsEnabled = false;
Cursor = Cursors.Wait;
Task<Boolean>.Factory.StartNew(InitConnection).ContinueWith(t =>
{
if (!t.Result)
return;
// Emulate some work after connection's been established
Thread.SpinWait(1000000000); (2)
_btnOk.IsEnabled = true;
Cursor = Cursors.Arrow;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
private Boolean InitConnection()
{
Thread.SpinWait(10000000); (1)
return true;
if (_isFirstTime)
{
// Emulate some work to establish connection
Thread.SpinWait(10000000); (1)
_isFirstTime = false;
}
return true;
}
Code placed above works fine. To emulate bug comment plz these strings in InitConnection method:
Thread.SpinWait(10000000); (1)
return true;
and click OK button many times. Now you can see that Cursor always works fine, it changes. But button's IsEnabled property doesn't work very often. I mean button stays Enabled.
Is it one more WPF-bug or has it any rational explanation?
.Net 4.0, Win 7
Using TaskScheduler.FromCurrentSynchronizationContext() will tell your task to execute on the UI thread.
When you call Thread.SpinWait(1000000000); the UI will become unresponsive (you will notice this if you try and do anything, such as drag the window around etc) and so the disabling of the button is not guaranteed.
So initially, I would suggest you remove TaskScheduler.FromCurrentSynchronizationContext(), but doing so will mean that you can't re-enable the button at the end of the Task as you will no longer be on the UI thread. If you rework your AutentificateClick method as follows, I think you will find it does what you want, and the UI will remain responsive whilst the task is executing.
private void AutentificateClick(Object sender, RoutedEventArgs e)
{
_btnOk.IsEnabled = false;
Cursor = Cursors.Wait;
Task<Boolean>.Factory.StartNew(InitConnection).ContinueWith(t =>
{
if (!t.Result)
return;
// Emulate some work after connection's been established
Thread.SpinWait(1000000000);
this.Dispatcher.Invoke((Action)(() =>
{
_btnOk.IsEnabled = true;
Cursor = Cursors.Arrow;
}));
});
}

ListPicker SelectionChanged Event Called Multiple Times During Navigation

I have a SelectionChanged event in a ListPicker within one of my application Pages that fires multiple times before the page is loaded. This is really inconvenient for me as when an item is selected, a MessageBox is displayed (and other actions will be performed). The MessageBox is displayed twice every time the page is NavigatedTo. How can I fix this?
XAML
<toolkit:ListPicker x:Name="ThemeListPicker" Header="Theme"
ItemTemplate="{StaticResource PickerItemTemplate}"
SelectionChanged="ThemeListPicker_SelectionChanged"/>
XAML.CS
private void ThemeListPicker_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
if(ThemeListPicker.SelectedIndex != -1)
{
var theme = (sender as ListPicker).SelectedItem;
if (index == 0)
{
Settings.LightTheme.Value = true;
MessageBox.Show("light");
}
else
{
Settings.LightTheme.Value = false;
MessageBox.Show("dark");
}
}
}
well, that's how a listpicker behaves, what best you can do is instead of making ThemeListPicker_SelectionChanged make a parent stackpanel inside the datatemplate somewhat like this
<Listpicker.ItemTemplate>
<DataTemplate x:Name="PickerItemTemplate">
<StackPanel tap="stk_Tap">
<TextBlock/>
</StackPanel>
</DataTemplate>
</Listpicker.ItemTemplate>
<Listpicker.FullModeItemTemplate>
<DataTemplate x:Name="PickerFullModeItemTemplate">
<StackPanel tap="stk_Tap">
<TextBlock/>
</StackPanel>
</DataTemplate>
<Listpicker.FullModeItemTemplate>
now use this tap stk_Tap to do your action as, this event would also get called every time the selection changed gets called but, it wont exhibit the buggy behavior like that of selection changed event.
hope this helps.
Attach the SelectionChanged event after the ListPicker is Loaded.
...
InitializeComponent();
YourListPicker.Loaded += YourListPicker_Loaded;
...
private void YourListPicker_Loaded(object sender, RoutedEventArgs e)
{
YourListPicker.SelectionChanged += YourListPicker_SelectionChanged;
}

Textblock Text property not being set using OnLoaded event

I Have a texblock on a specific page. I am navigation from a previous page where one a selection from a listbox is clicked, the object is stored the the APP.Xaml file as a property. On my navigating page, i call this object in the Loaded event for my listboxes and set their text property to their respective equivalents on my stored property from my App.xaml file.
The problem is, only one of 4 listboxes works while the other fail to show up. Anyone know why this may be. My code is below.
private void StopNameTextBlock_Loaded(object sender, RoutedEventArgs e)
{
StopNameTextBlock.Text = (Application.Current as App).SelectedBusStop.StopName;
}
private void BusDirectionTextBlock_Loaded(object sender, RoutedEventArgs e)
{
BusDirectionTextBlock.Text = "Towards: " + (Application.Current as App).SelectedBusStop.BusDirection;
}
private void BusesServedTextBlock_Loaded(object sender, RoutedEventArgs e)
{
BusesServedTextBlock.Text = "Buses Served: " + (Application.Current as App).SelectedBusStop.BusesServed;
}
From the code above. the first text block populates but the others do not. Help
Below is the XAML code.
<StackPanel Name="StopInfo" Orientation="Vertical" Margin="0,0,0,620">
<TextBlock Name="BusDirectionTextBlock" Loaded="BusDirectionTextBlock_Loaded" />
<TextBlock Name="BusesServedTextBlock" Loaded="BusesServedTextBlock_Loaded" />
<TextBlock Name="TimeRequestedTextBlock" />
</StackPanel>

Categories