Hidden layer on top of iOS MAUI .NET 7.0 - c#

Im having this problem where my iOS app deployed but there's like an invisible wall that's not letting me click anything(this is for iOS only). On android everything runs perfect nothing broken as far as I can see
here is the login page xaml:
<Grid Padding="0" VerticalOptions="Center" RowDefinitions="Auto,Auto">
<Image x:Name="ImageLogo" Source="rcm_white_red_transpar.png" VerticalOptions="Center" WidthRequest="350" HeightRequest="104"/>
<VerticalStackLayout VerticalOptions="Center" Grid.Row="1" Padding="10">
<Entry x:Name="PhoneNumber" Keyboard="Telephone" BackgroundColor="White" Placeholder="Phone Number XXX-XXX-XXXX" HorizontalTextAlignment="Center" HeightRequest="50" WidthRequest="320" TextColor="Black" PlaceholderColor="SlateGray"/>
<Entry x:Name="PinNumber" Keyboard="Numeric" BackgroundColor="White" Placeholder="PIN Number XXXXXX" HorizontalTextAlignment="Center" HeightRequest="50" WidthRequest="320" IsVisible="false" TextColor="Black" PlaceholderColor="SlateGray"/>
<Button x:Name="ButtonSignIn" HeightRequest="50" WidthRequest="320" HorizontalOptions="Center" VerticalOptions="Center" FontSize="Large" Text="Sign In" Clicked="ButtonSignIn_Clicked" IsVisible="false" />
<Button x:Name="ButtonSendCode" HeightRequest="50" WidthRequest="320" HorizontalOptions="Center" VerticalOptions="Center" FontSize="Large" Text="Send PIN" Clicked="ButtonSendCode_Clicked" IsVisible="true" Margin="0,10,0,0"/>
</VerticalStackLayout>
<ActivityIndicator Color="{StaticResource Primary}" x:Name="LoginActivityIndicator" IsRunning="False"/>
</Grid>
here's the login page xaml.cs:
string logout_message_ = null;
public string logout_message
{
set
{
logout_message_ = value;
}
}
public LoginPage()
{
InitializeComponent();
this.BindingContext = new LoginViewModel();
http_client_ = App.GetBackendClient();
// In the accept header value, we tell that JSON is an acceptable response type.
http_client_.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// load our last phone number used
var task = SecureStorage.GetAsync("last_phone_number");
task.Wait();
var token = task.Result;
if (token != null)
{
PhoneNumber.Text = token;
}
}
//Override the OnAppearing function to load our MSAL
protected override async void OnAppearing()
{
//Disable the Flyout so user's cant get in unwarranted
Shell.Current.FlyoutBehavior = FlyoutBehavior.Disabled;
//show activity
LoginActivityIndicator.IsRunning = true;
if (logout_message_ != null)
{
await DisplayAlert("Logged Out", logout_message_, "Dismiss");
}
//Authenticate
try
{
var oauthToken = await App.GetCachedLogin();
if (oauthToken != null)
{
await App.Login(oauthToken);
await Shell.Current.GoToAsync($"//{nameof(HomePage)}");
}
}
catch (Exception ex)
{
// Do nothing - the user isn't logged in
await DisplayAlert("Error", ex.Message, "Dismiss");
}
LoginActivityIndicator.IsRunning = false;
base.OnAppearing();
}
//Override the OnDisappearing function to enable the Flyout when leaving
protected override void OnDisappearing()
{
Shell.Current.FlyoutBehavior = FlyoutBehavior.Flyout;
LoginActivityIndicator.IsRunning = false;
base.OnDisappearing();
}
//Button Send Code Command in LoginPage.xaml
private async void ButtonSendCode_Clicked(object sender, EventArgs e)
{
string phone_number = RemoveNonNumeric(PhoneNumber.Text);
string requestUri = "api/Login/start/" + phone_number + "/" + Constants.LoginSecretKey;
try
{
//send api call to backend
//if successful then unhide other buttons
HttpResponseMessage response = await http_client_.GetAsync(requestUri);
if (response.IsSuccessStatusCode)
{
ButtonSignIn.IsVisible = true;
PinNumber.IsVisible = true;
ButtonSendCode.Text = "Resend PIN";
// We generate a request and read the content asynchronously
string content = await response.Content.ReadAsStringAsync();
}
else if (response.StatusCode == (System.Net.HttpStatusCode)429)
{
await DisplayAlert("Error", "Too many pin requests. Please wait before trying again.", "Dismiss");
}
else
{
await DisplayAlert("Error", (int)response.StatusCode + ": " + response.ReasonPhrase, "Dismiss");
}
}
catch (Exception ex)
{
await DisplayAlert("An error has occurred", ex.Message, "Dismiss");
}
}
private async void ButtonSignIn_Clicked(object sender, EventArgs e)
{
string phone_number = RemoveNonNumeric(PhoneNumber.Text);
string user_pin = PinNumber.Text;
string requestUri = "api/Login/finish/" + phone_number + "/" + user_pin;
try
{
// need to call the phone login finish API and if successful with a token returned the login
HttpResponseMessage response = await http_client_.GetAsync(requestUri);
if (response.IsSuccessStatusCode)
{
// We generate a request and read the content asynchronously
var token = await response.Content.ReadAsStringAsync();
await App.Login(System.Text.Json.JsonSerializer.Deserialize<string>(token));
// save the last valid phone login for use on future launches
await SecureStorage.SetAsync("last_phone_number", PhoneNumber.Text);
PinNumber.Text = "";// clear our pin
await Shell.Current.GoToAsync($"//{nameof(HomePage)}");
}
else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
await DisplayAlert("Error", "Incorrect pin or phone number.", "Dismiss");
}
else
{
await DisplayAlert("Error", (int)response.StatusCode + ": " + response.ReasonPhrase, "Dismiss");
}
}
catch (Exception ex)
{
//Debug.WriteLine(ex.Message);
await DisplayAlert("An error has occurred", ex.Message, "Dismiss");
}
}
public static string RemoveNonNumeric(string text)
{
string newText = "";
if (String.IsNullOrEmpty(text))
{
return newText;
}
newText = Regex.Replace(text, "[^0-9]", "");
return newText;
}
Here's the app shell too if its helpful:
<!--
The overall app visual hierarchy is defined here, along with navigation.
https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/shell/
-->
<Shell.Resources>
<ResourceDictionary>
<DataTemplate x:Key="MenuItemRobotModeOff">
<Label x:Name="LabelRobotMode"
Text="Robot Mode OFF"
IsVisible="false"
HeightRequest="40"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
FontSize="Large"
BackgroundColor="{StaticResource Primary}"
TextColor="{StaticResource Secondary}"/>
</DataTemplate>
<DataTemplate x:Key="MenuItemRobotModeOn">
<Label Text="Robot Mode ON"
HeightRequest="40"
IsVisible="false"
HorizontalTextAlignment="Center"
VerticalTextAlignment="Center"
FontSize="Large"
BackgroundColor="{StaticResource Active}"
TextColor="{StaticResource Secondary}"/>
</DataTemplate>
<Style x:Key="BaseStyle" TargetType="Element">
<Setter Property="Shell.BackgroundColor" Value="{StaticResource Primary}"/>
<Setter Property="Shell.ForegroundColor" Value="{StaticResource Secondary}"/>
<Setter Property="Shell.TitleColor" Value="{StaticResource Secondary}"/>
<Setter Property="Shell.DisabledColor" Value="#B4FFFFFF"/>
<Setter Property="Shell.UnselectedColor" Value="#95FFFFFF"/>
<Setter Property="Shell.TabBarBackgroundColor" Value="{StaticResource Primary}"/>
<Setter Property="Shell.TabBarForegroundColor" Value="{StaticResource Secondary}"/>
<Setter Property="Shell.TabBarUnselectedColor" Value="#95FFFFFF"/>
<Setter Property="Shell.TabBarTitleColor" Value="{StaticResource Secondary}"/>
</Style>
<Style TargetType="TabBar" BasedOn="{StaticResource BaseStyle}"/>
<Style TargetType="FlyoutItem" BasedOn="{StaticResource BaseStyle}"/>
<!--
Default Styles for all Flyout Items
https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/shell/flyout#flyoutitem-and-menuitem-style-classes
-->
<Style Class="FlyoutItemLabelStyle" TargetType="Label">
<Setter Property="TextColor" Value="{StaticResource Secondary}"></Setter>
<!-- Change font for FlyoutItem to Custom Font -->
<Setter Property="FontFamily" Value="DINOTMedium.otf#DINOTMedium.otf"></Setter>
<Setter Property="TextTransform" Value="Uppercase"/>
</Style>
<Style Class="FlyoutItemLayoutStyle" TargetType="Layout" ApplyToDerivedTypes="True">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="{x:OnPlatform iOS={AppThemeBinding Dark={StaticResource Accent}, Light={StaticResource Secondary},Default={StaticResource Accent}}, Android={AppThemeBinding Dark={StaticResource Accent}, Light={StaticResource Secondary},Default={StaticResource Accent}}}"/>
<Setter TargetName="FlyoutItemLabel" Property="Label.TextColor" Value="{StaticResource Primary}"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="{StaticResource Primary}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<!--Custom Style you can apply to any Flyout Item-->
<Style Class="MenuItemLayoutStyle" TargetType="Layout" >
<!--ApplyToDerivedTypes="True"-->
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter TargetName="FlyoutItemLabel" Property="Label.TextColor" Value="{StaticResource Primary}"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<!--Custom Style for Robot Mode menu item -->
<Style Class="MenuItemRobotModeOnStyle" TargetType="Layout" ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor" Value="{StaticResource Active}"/>
</Style>
<Style Class="MenuItemRobotModeOffStyle" TargetType="Layout" ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor" Value="{StaticResource Primary}"/>
</Style>
</ResourceDictionary>
</Shell.Resources>
<!--Default login Page-->
<ShellItem Route="Login">
<ShellContent ContentTemplate="{DataTemplate local:LoginPage}"/>
</ShellItem>
<!--
When the Flyout is visible this defines the content to display in the flyout.
FlyoutDisplayOptions="AsMultipleItems" will create a separate flyout item for each child element
https://docs.microsoft.com/dotnet/api/xamarin.forms.shellgroupitem.flyoutdisplayoptions?view=xamarin-forms
-->
<FlyoutItem x:Name="HomePage" Title="Home" Icon="{AppThemeBinding Dark=home_white.png,Light=home.png}">
<Tab>
<ShellContent Route="HomePage" ContentTemplate="{DataTemplate local:HomePage}"/>
</Tab>
</FlyoutItem>
<FlyoutItem x:Name="MowersPage" Title="Machines" Icon="{AppThemeBinding Dark=machine_white.png,Light=machine.png}">
<ShellContent Route="MowersPage" ContentTemplate="{DataTemplate local:MowersPage}"/>
</FlyoutItem>
<FlyoutItem x:Name="PlanPage" Title="Plan" Icon="{AppThemeBinding Dark=plan_white.png,Light=plan.png}">
<ShellContent Route="PlanPage" ContentTemplate="{DataTemplate local:PlanPage}" />
</FlyoutItem>
<FlyoutItem x:Name="MonitorPage" Title="Monitor" Icon="{AppThemeBinding Dark=monitor_white.png,Light=monitor.png}">
<ShellContent Route="MonitorPage" ContentTemplate="{DataTemplate local:MonitorPage}" />
</FlyoutItem>
<FlyoutItem x:Name="MowPage" Title="Mow" Icon="{AppThemeBinding Dark=mow_white.png,Light=mow.png}">
<ShellContent Route="MowPage" ContentTemplate="{DataTemplate local:MowPage}"/>
</FlyoutItem>
<!-- When the Flyout is visible this will be a menu item you can tie a click behavior to -->
<MenuItem x:Name="LogoutMenuItem" Text="Logout" StyleClass="MenuItemLayoutStyle" Clicked="OnMenuItemLogout_Clicked" />
<!--
TabBar lets you define content that won't show up in a flyout menu. When this content is active
the flyout menu won't be available. This is useful for creating areas of the application where
you don't want users to be able to navigate away from. If you would like to navigate to this
content you can do so by calling
await Shell.Current.GoToAsync("//LoginPage");
-->
<TabBar>
<ShellContent Route="LoginPage" ContentTemplate="{DataTemplate local:LoginPage}" />
</TabBar>
<!-- Optional Templates
// These may be provided inline as below or as separate classes.
// This header appears at the top of the Flyout.
// https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/shell/flyout#flyout-header
<Shell.FlyoutHeaderTemplate>
<DataTemplate>
<Grid>ContentHere</Grid>
</DataTemplate>
</Shell.FlyoutHeaderTemplate>
// ItemTemplate is for ShellItems as displayed in a Flyout
// https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/shell/flyout#define-flyoutitem-appearance
<Shell.ItemTemplate>
<DataTemplate>
<ContentView>
Bindable Properties: Title, Icon
</ContentView>
</DataTemplate>
</Shell.ItemTemplate>
// MenuItemTemplate is for MenuItems as displayed in a Flyout
// https://docs.microsoft.com/xamarin/xamarin-forms/app-fundamentals/shell/flyout#define-menuitem-appearance
<Shell.MenuItemTemplate>
<DataTemplate>
<ContentView>
Bindable Properties: Text, Icon
</ContentView>
</DataTemplate>
</Shell.MenuItemTemplate>
-->
<Shell.FlyoutFooter>
<StackLayout>
<Image x:Name="CustomerSupportImage" Source="{AppThemeBinding Dark={OnPlatform iOS=rcm_badge_horiz_cust_supp_phone.png, Android=Images/rcm_badge_horiz_cust_supp_phone.png}, Light={OnPlatform iOS=rcm_badge_horiz_cust_supp_phone_invert.png, Android=Images/rcm_badge_horiz_cust_supp_phone_invert.png},Default={OnPlatform iOS=rcm_badge_horiz_cust_supp_phone.png, Android=Images/rcm_badge_horiz_cust_supp_phone.png}}" WidthRequest="250" />
<Image Source="{AppThemeBinding Dark={OnPlatform iOS=rcm_white_red_transpar.png, Android=Images/rcm_white_red_transpar.png}, Light={OnPlatform iOS=rcm_black_red_transpar.png, Android=Images/rcm_black_red_transpar.png},Default={OnPlatform iOS=rcm_white_red_transpar.png, Android=Images/rcm_white_red_transpar.png}}" WidthRequest="40" HeightRequest="40" Margin="20"/>
</StackLayout>
</Shell.FlyoutFooter>
I tried downloading vs on my Mac book to see if the problem was with the simulator and no luck. I even tried it on my iPhone and the invisible wall is still present

For debugging, you could set a background to see if ActivityIndicator covers other control. For covered control, you could not access it by clicking it.
An workaround is to set the IsVisible property. When ActivityIndicator is running set IsVisible to True. When ActivityIndicator is stopped set IsVisible to False.
LoginActivityIndicator.IsVisible = LoginActivityIndicator.IsRunning;
Hope it works for you.

Related

How to disable all toggle buttons except one that clicked?

I'm making an application using C# and WPF
I have 8 toggle buttons.
When I click one of the buttons others should be disabled so I can't click it except one is activated.
When I click this button again others should be enabled
Styles.xaml:
<!--Toggle Button-->
<Style x:Key="ToggleButton" TargetType="{x:Type ToggleButton}">
<Setter Property="Background" Value="#535353"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="MinHeight" Value="30"/>
<Setter Property="Grid.Column" Value="1"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="{TemplateBinding Background}"
BorderThickness="0">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow.xaml:
<ToggleButton x:Name="CategoryToggle1"
Grid.Row="1"
Style="{StaticResource ToggleButton}"
Checked="ShowOptions"
Unchecked="HideOptions" />
How Can I achieve this by code and XAML?
I want to do it like in this video:
Video
Thank you for comments guys, I found another solution.
MainWindow.XAML:
<RadioButton x:Name="CategoryToggle1"
Grid.Row="1"
Grid.Column="1"
Style="{StaticResource RadioButton}"
GroupName="ToggleButtonsGroup"
Checked="OpenOptions"
Unchecked="HideOptions"/>
Styles.xaml:
<!--Radio Button-->
<Style TargetType="RadioButton"
x:Key="RadioButton"
BasedOn="{StaticResource {x:Type ToggleButton}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ToggleButton IsChecked="{Binding IsChecked, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--Toggle Button-->
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Background" Value="#535353" />
<Setter Property="MinHeight" Value="30" />
<Setter Property="Grid.Column" Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Border Background="{TemplateBinding Background}"
BorderThickness="0">
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
<Trigger Property="IsEnabled" Value="true">
<Setter Property="Opacity" Value="1" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow.cs:
private void OpenOptions(object sender, RoutedEventArgs e){
RadioButton radioButton = sender as RadioButton;
radioButton.IsChecked = true;
//Disable all option buttons except one that active
MyGrid.Children.OfType<RadioButton>().Where(rb => rb != radioButton &&
rb.GroupName == radioButton.GroupName).ToList().ForEach(rb => rb.IsEnabled = false);
}
private void HideOptions(object sender, RoutedEventArgs e)
{
RadioButton radioButton = sender as RadioButton;
MyGrid.Children.OfType<RadioButton>().Where(rb => rb.GroupName ==
radioButton.GroupName).ToList().ForEach(rb => rb.IsEnabled = true);
}
Using Click events of each ToggleButton
One way you could do it is by giving a name to all your ToggleButtons, hook-up to their Click event and manually uncheck others in the code-behind:
XAML
<StackPanel Orientation="Vertical">
<StackPanel.Resources>
<Style TargetType="ToggleButton">
<Style.Triggers>
<Trigger Property="IsChecked" Value="False">
<Setter Property="MaxWidth" Value="15"/>
</Trigger>
</Style.Triggers>
</Style>
</StackPanel.Resources>
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="button1" Content="Button 1" Click="button1_Click"/>
<TextBlock Text="Line 1"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="button2" Content="Button 2" Click="button2_Click"/>
<TextBlock Text="Line 2"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="button3" Content="Button 3" Click="button3_Click"/>
<TextBlock Text="Line 3"/>
</StackPanel>
</StackPanel>
Code-behind
private void button1_Click(object sender, RoutedEventArgs e) {
if (button1.IsChecked == true) {
button2.IsChecked = false;
button3.IsChecked = false;
}
}
private void button2_Click(object sender, RoutedEventArgs e) {
if (button2.IsChecked == true) {
button1.IsChecked = false;
button3.IsChecked = false;
}
}
private void button3_Click(object sender, RoutedEventArgs e) {
if (button3.IsChecked == true) {
button1.IsChecked = false;
button2.IsChecked = false;
}
}
This method is tedious, error-prone, requires code-behind and is not very scalable.
Binding IsChecked properties to a collection of bool with one true at a time.
Another way you could go (still by using code-behind) is to define a collection of boolean values and bind each ToggleButton.IsChecked on one of the bool in the collection, and ensure that the collection only contains at most one true at a time:
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="button1" Content="Button 1" IsChecked="{Binding [0]}"/>
<TextBlock Text="Line 1"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="button2" Content="Button 2" IsChecked="{Binding [1]}"/>
<TextBlock Text="Line 2"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<ToggleButton x:Name="button3" Content="Button 3" IsChecked="{Binding [2]}"/>
<TextBlock Text="Line 3"/>
</StackPanel>
Code-behind
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
ObservableCollection<bool> states = new ObservableCollection<bool> { false, false, false };
states.CollectionChanged += States_CollectionChanged;
DataContext = states;
}
private void States_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
var collection = sender as ObservableCollection<bool>;
if (e.Action == NotifyCollectionChangedAction.Replace) {
if ((bool)e.NewItems[0]) {
for (int i = 0; i < collection.Count; i++) {
if (e.NewStartingIndex != i) {
collection[i] = false;
}
}
}
}
}
}
Again, this uses code-behind and not the view model but at least it is easier to add rows.
The behavior you need is very specific.
I don't know how, but i got here trying to make a toggle button behave like a radio button. Your answer was enlightning.
For what it's worth, here's how you would do that :
Resource :
<Style x:Key='RadioToggle' TargetType='RadioButton'
BasedOn='{StaticResource {x:Type ToggleButton}}' />
Control :
<RadioButton Content='RadioToggle1' IsChecked='True'
Style='{StaticResource RadioToggle}'
GroupName="RadioToggleGroup" />
<RadioButton Content='RadioToggle2'
Style='{StaticResource RadioToggle}'
GroupName="RadioToggleGroup" />
<RadioButton Content='RadioToggle3'
Style='{StaticResource RadioToggle}'
GroupName="RadioToggleGroup" />

Unhandled exception `The parameter is incorrect` in NotifyCollectionChangedEvent handler

I have a very simple ListView whose ItemsSource is a ObservableCollection. Better show it with code:
MainPage.xaml:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Windows.UI.Xaml.Shapes"
x:Class="Test.MainPage" Background="Black" >
<Grid x:Name="Board" Background="Transparent" >
<ListView ItemsSource="{x:Bind LineList}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Line">
<StackPanel Orientation="Horizontal" Spacing="5">
<TextBlock Foreground="White" Text="{x:Bind Name}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Mainpage.xaml.cs:
public sealed partial class MainPage : Page
{
public ObservableCollection<Line> LineList = new ObservableCollection<Line>();
public MainPage()
{
InitializeComponent();
LineList.CollectionChanged += List_CollectionChanged;
LineList.Add(new Line { Name = "Line1" });
LineList.Add(new Line { Name = "Line2" });
}
private void List_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if(e.Action == NotifyCollectionChangedAction.Add)
{
Board.Children.Add(e.NewItems[0] as Line);//if I comment out this line, no exception
}
}
}
What I actually want is that, when I add a Line on the ListView to show it's Name, it be also added in the Grid as an actual Shape. Note that, I am using the ListView only to show the Names of those Lines, and in the Grid I want an actual Line Shape
I don't know what I've done wrong, but the above attempt gives the stated Exception.
If these informations help:
No Exception occurs if I don't add the Line in the Grid
No Exception if : Board.Children.Add(new Line { Name = "Line2" });
I've been fiddling around with your code and I was able to track down what is wrong with your code. However I'm not really sure why it's happening.
The reason why you're getting errors is because you're trying to use same instance of an UIElement (i.e. Line) that you're binding to your ListView.ItemsSource. Why it's failing, is a bit of mystery to me. I suspect that it's forbidden to Bind and add the same UIElement to XAML, as it might create binding loops!? That's just a wild guess though. Anyways...
You shouldn't be using UIElement as the binding context - I can't think of any scenario that you would do such thing. You will be better off by creating a separate model, as per my previous answer (e.g. LineViewModel), and using that as your BindingContext. Your MainPage.xaml.cs code could look like this:
public sealed partial class MainPage : Page
{
public ObservableCollection<LineViewModel> Lines = new ObservableCollection<LineViewModel>();
public MainPage()
{
InitializeComponent();
Lines.CollectionChanged += LinesOnCollectionChanged;
Lines.Add(new LineViewModel { Name = "Line1" });
Lines.Add(new LineViewModel { Name = "Line2" });
}
private void LinesOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
MainGrid.Children.Add(new Line()
{
Name = (e.NewItems[0] as LineViewModel)?.Name ?? string.Empty,
Stroke = new SolidColorBrush(Colors.Black),
StrokeThickness = 12,
X1 = 0,
X2 = 10000
});
}
}
}
public class LineViewModel
{
public string Name { get; set; }
}
The MainPage.xaml will stay the same, as per my previous answer
I'm not sure if this is what you're after but here it goes
MainPage.xaml
<Page x:Class="App4.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App4"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<Style x:Key="LineViewItemContainerStyle"
TargetType="ListViewItem">
<Setter Property="FontFamily"
Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize"
Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="Background"
Value="{ThemeResource ListViewItemBackground}" />
<Setter Property="Foreground"
Value="{ThemeResource ListViewItemForeground}" />
<Setter Property="TabNavigation"
Value="Local" />
<Setter Property="IsHoldingEnabled"
Value="True" />
<Setter Property="Padding"
Value="0" />
<Setter Property="HorizontalContentAlignment"
Value="Stretch" />
<Setter Property="VerticalContentAlignment"
Value="Stretch" />
<Setter Property="MinWidth"
Value="{ThemeResource ListViewItemMinWidth}" />
<Setter Property="MinHeight"
Value="{ThemeResource ListViewItemMinHeight}" />
<Setter Property="AllowDrop"
Value="False" />
<Setter Property="UseSystemFocusVisuals"
Value="True" />
<Setter Property="FocusVisualMargin"
Value="0" />
<Setter Property="FocusVisualPrimaryBrush"
Value="{ThemeResource ListViewItemFocusVisualPrimaryBrush}" />
<Setter Property="FocusVisualPrimaryThickness"
Value="2" />
<Setter Property="FocusVisualSecondaryBrush"
Value="{ThemeResource ListViewItemFocusVisualSecondaryBrush}" />
<Setter Property="FocusVisualSecondaryThickness"
Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ListViewItemPresenter x:Name="Root"
CheckBrush="{ThemeResource ListViewItemCheckBrush}"
ContentMargin="{TemplateBinding Padding}"
CheckBoxBrush="{ThemeResource ListViewItemCheckBoxBrush}"
ContentTransitions="{TemplateBinding ContentTransitions}"
CheckMode="{ThemeResource ListViewItemCheckMode}"
DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}"
DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}"
DragBackground="{ThemeResource ListViewItemDragBackground}"
DragForeground="{ThemeResource ListViewItemDragForeground}"
FocusVisualSecondaryBrush="{TemplateBinding FocusVisualSecondaryBrush}"
FocusVisualPrimaryThickness="{TemplateBinding FocusVisualPrimaryThickness}"
FocusVisualSecondaryThickness="{TemplateBinding FocusVisualSecondaryThickness}"
FocusBorderBrush="{ThemeResource ListViewItemFocusBorderBrush}"
FocusVisualMargin="{TemplateBinding FocusVisualMargin}"
FocusVisualPrimaryBrush="{TemplateBinding FocusVisualPrimaryBrush}"
FocusSecondaryBorderBrush="{ThemeResource ListViewItemFocusSecondaryBorderBrush}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
Control.IsTemplateFocusTarget="True"
PressedBackground="{ThemeResource ListViewItemBackgroundPressed}"
PlaceholderBackground="{ThemeResource ListViewItemPlaceholderBackground}"
PointerOverForeground="{ThemeResource ListViewItemForegroundPointerOver}"
PointerOverBackground="{ThemeResource ListViewItemBackgroundPointerOver}"
ReorderHintOffset="{ThemeResource ListViewItemReorderHintThemeOffset}"
SelectedForeground="{ThemeResource ListViewItemForegroundSelected}"
SelectionCheckMarkVisualEnabled="{ThemeResource ListViewItemSelectionCheckMarkVisualEnabled}"
SelectedBackground="{ThemeResource ListViewItemBackgroundSelected}"
SelectedPressedBackground="{ThemeResource ListViewItemBackgroundSelectedPressed}"
SelectedPointerOverBackground="{ThemeResource ListViewItemBackgroundSelectedPointerOver}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Selected" />
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="Root.(RevealBrush.State)"
Value="PointerOver" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOverSelected">
<VisualState.Setters>
<Setter Target="Root.(RevealBrush.State)"
Value="PointerOver" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PointerOverPressed">
<VisualState.Setters>
<Setter Target="Root.(RevealBrush.State)"
Value="Pressed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Target="Root.(RevealBrush.State)"
Value="Pressed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="PressedSelected">
<VisualState.Setters>
<Setter Target="Root.(RevealBrush.State)"
Value="Pressed" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="DisabledStates">
<VisualState x:Name="Enabled" />
<VisualState x:Name="Disabled">
<VisualState.Setters>
<Setter Target="Root.RevealBorderThickness"
Value="0" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ListViewItemPresenter>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<Grid Background="Transparent">
<ListView ItemContainerStyle="{StaticResource LineViewItemContainerStyle}"
ItemsSource="{x:Bind Lines, Mode=OneTime}"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:LineViewModel">
<Grid x:Name="ItemGrid">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Foreground="Black"
Text="{x:Bind Name, Mode=OneWay}" />
<Border Grid.Row="1"
BorderBrush="Black"
BorderThickness="1" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public ObservableCollection<LineViewModel> Lines = new ObservableCollection<LineViewModel>();
public MainPage()
{
InitializeComponent();
Lines.Add(new LineViewModel { Name = "Line1" });
Lines.Add(new LineViewModel { Name = "Line2" });
}
}
public class LineViewModel
{
public string Name { get; set; }
}
Note, that instead of using Line I used Border. Also, I needed to override base ListViewItemContainerStyle to set HorizontalContentAlignment and VerticalContentAlignment to Stretch, so that the DataTemplate elements can take up the entire space of the item
The result:

WPF application access controls in code-behind

I'm workin on a WPF scorekeeping app. So far the UI is done, but I can't access any of the controls in the code-behind. Here's the XAML:
<Window x:Class="Scoreboard.MainScoreBoard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DartsLeague" Height="720" Width="1280">
<Grid x:Name="MainGrid" Background="DarkGreen">
<StackPanel x:Name="Player1" HorizontalAlignment="Left" VerticalAlignment="Stretch">
<Label x:Name="Player1Name" Style="{StaticResource PlayerName}"/>
<Label x:Name="Player1Score" Style="{StaticResource Score}"/>
<ScrollViewer x:Name="Player1Throws" Height="380" FlowDirection="RightToLeft">
</ScrollViewer>
</StackPanel>
<StackPanel x:Name="Player2" HorizontalAlignment="Right" VerticalAlignment="Stretch">
<Label x:Name="Player2Name" Style="{StaticResource PlayerName}">Player 2</Label>
<Label x:Name="Player2Score" Style="{StaticResource Score}">501</Label>
<ScrollViewer x:Name="Player2Throws" Height="380">
</ScrollViewer>
</StackPanel>
<StackPanel x:Name="Input" HorizontalAlignment="Center" VerticalAlignment="Top">
<TextBox x:Name="CurrentNumber" MaxLength="3" Width="160" Margin="20" FontSize="30" TextChanged="TextBox_TextChanged"></TextBox>
<Button x:Name="SubmitButton" IsDefault="True" Style="{StaticResource Golden} " Click="Button_Click">Запиши резултат</Button>
<Button x:Name="CancelButton" Style="{StaticResource Golden}">Върни назад</Button>
</StackPanel>
<Grid x:Name="StatsGrid" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="60" Width="560">
<StackPanel x:Name="Player1Stats" HorizontalAlignment="Left" VerticalAlignment="Stretch">
<Label x:Name="Player1Legs" Style="{StaticResource BoardStats}"/>
<Label x:Name="Player1Sets" Style="{StaticResource BoardStats}"/>
<Label x:Name="Player1Avr" Style="{StaticResource BoardStats}"/>
</StackPanel>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Stretch">
<Label Style="{StaticResource BoardStats}">LEGS</Label>
<Label Style="{StaticResource BoardStats}">SETS</Label>
<Label Style="{StaticResource BoardStats}">3 DART AVERAGE</Label>
</StackPanel>
<StackPanel x:Name="Player2Stats" HorizontalAlignment="Right" VerticalAlignment="Stretch">
<Label x:Name="Player2Legs" Style="{StaticResource BoardStats}"/>
<Label x:Name="Player2Sets" Style="{StaticResource BoardStats}"/>
<Label x:Name="Player2Avr" Style="{StaticResource BoardStats}"/>
</StackPanel>
</Grid>
</Grid>
</Window>
App.xaml:
<Application x:Class="Scoreboard.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Scoreboard"
StartupUri="StartWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<Style x:Key="Golden" TargetType="Button">
<Setter Property="Background" Value="Gold" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="Margin" Value="10" />
<Setter Property="Padding" Value="4" />
<Setter Property="FontSize" Value="30" />
</Style>
<Style x:Key="PlayerName" TargetType="Label">
<Setter Property="Background" Value="White" />
<Setter Property="Margin" Value="10"/>
<Setter Property="FontSize" Value="34" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<Style x:Key="Score" TargetType="Label">
<Setter Property="Margin" Value="10" />
<Setter Property="Background" Value="White" />
<Setter Property="FontSize" Value="160" />
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<Style x:Key="BoardStats" TargetType="Label">
<Setter Property="Background" Value="Beige" />
<Setter Property="Margin" Value="4" />
<Setter Property="BorderBrush" Value="DarkRed" />
<Setter Property="BorderThickness" Value="4" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="26" />
<Setter Property="Padding" Value="8" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
Heres the C# Code, It probably has more bugs that I know of, but it won't compile because it doesn't recognize any of the names from the XAML:
namespace Scoreboard
{
public partial class MainScoreBoard : Window
{
public MainScoreBoard()
{
InitializeComponent();
}
static bool IsTextAllowed(string text, int num)
{
Regex regex = new Regex("[^0-9.-]+");
if (!regex.IsMatch(text))
{
num = Int32.Parse(text);
return true;
}
else return false;
}
void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
}
void Button_Click(object sender, RoutedEventArgs e)
{
// You should get in the habit of initializing your variables
int currentScore = 0, currentPlayerScore, currentResult, legs, sets;
bool Player1Turn = true;
// You were getting null reference exceptions from attempting to access the Content property
// without them ever getting set. You can initialize them with default values in the xaml or in the
// constructor on initialization (After InitializeComponent() call).
if (Player1Score.Content != null && Player2Score.Content != null)
{
bool isGameOver = false;
// This will loop infinitely if you don't fix the bugs in calculating the score, I put a comment on it as you are not getting a value for currentScore from anywhere
while (Int32.Parse(Player1Score.Content.ToString()) != 0 || Int32.Parse(Player2Score.Content.ToString()) != 0)
{
if (IsTextAllowed(CurrentNumber.Text, currentScore))
{
if (currentScore > 180)
{
MessageBox.Show("Написаното е над 180!!!");
continue;
}
if (Player1Turn)
{
Player1Turn = false;
currentPlayerScore = Int32.Parse(Player1Score.Content.ToString());
// The currentScore variable is never getting set, I initialized it for you but
// it needs to be assigned a value from somewhere otherwise it will always be
// currentResult = currentPlayerScore - 0
currentResult = currentPlayerScore - currentScore;
if (currentResult < 0)
{
MessageBox.Show("Предобри!!!");
continue;
}
else if (currentResult == 0)
{
MessageBox.Show("Game Over!!!");
legs = Int32.Parse(Player1Legs.Content.ToString());
legs++;
Player1Legs.Content = legs.ToString();
if (legs == 3)
{
//increas sets
//if sets == 3, end game and shut down app
}
//Application.Current.Shutdown();
// Set flag so we do not keep looping through game over code
isGameOver = true;
}
Player1Score.Content = currentResult.ToString();
// Added this check here because I'm assuming you would want Player1Score to be reflected first
// before exiting the loop.
if (isGameOver)
{
// game is over, we do not want to keep showing the Game Over message box
break;
}
}
else
{
//the same for Player2
Player1Turn = true;
}
}
}
}
}
}
}
All of the controls have an x:Name on them, but I can access only 2 ot 3 of them, and even if I can, nothing happens. I want to write text in the textbox, and after some processing goes on with the textbox, some output and stats are supposed to show up on the labels, but it won't let me access any of it, or if it does, it's random, and nothing happens, as if there is no code there.
I cannot see the code for your Window declaration in the xaml. But since you renamed the namespace and class, you should have this in your xaml window declaration.
x:Class="Scoreboard.MainScoreBoard"
Essentially your xaml should look like this
<Window x:Class="Scoreboard.MainScoreBoard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
</Window>
You do have a lot more bugs in the code behind as you need to call .ToString() in several places as well as some null reference exceptions, but if you get stuck in there you can always post a new question.
public partial class MainScoreBoard : Window
{
public MainScoreBoard()
{
InitializeComponent();
}
static bool IsTextAllowed(string text, int num)
{
Regex regex = new Regex("[^0-9.-]+");
if (!regex.IsMatch(text))
{
num = Int32.Parse(text);
return true;
}
else return false;
}
void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
}
void Button_Click(object sender, RoutedEventArgs e)
{
// You should get in the habit of initializing your variables
int currentScore = 0, currentPlayerScore, currentResult, legs, sets;
bool Player1Turn = true;
// You were getting null reference exceptions from attempting to access the Content property
// without them ever getting set. You can initialize them with default values in the xaml or in the
// constructor on initialization (After InitializeComponent() call).
if (Player1Score.Content != null && Player2Score.Content != null)
{
bool isGameOver = false;
// This will loop infinitely if you don't fix the bugs in calculating the score, I put a comment on it as you are not getting a value for currentScore from anywhere
while (Int32.Parse(Player1Score.Content.ToString()) != 0 || Int32.Parse(Player2Score.Content.ToString()) != 0)
{
if (IsTextAllowed(CurrentNumber.Text, currentScore))
{
if (currentScore > 180)
{
MessageBox.Show("Написаното е над 180!!!");
continue;
}
if (Player1Turn)
{
Player1Turn = false;
currentPlayerScore = Int32.Parse(Player1Score.Content.ToString());
// The currentScore variable is never getting set, I initialized it for you but
// it needs to be assigned a value from somewhere otherwise it will always be
// currentResult = currentPlayerScore - 0
currentResult = currentPlayerScore - currentScore;
if (currentResult < 0)
{
MessageBox.Show("Предобри!!!");
continue;
}
else if (currentResult == 0)
{
MessageBox.Show("Game Over!!!");
legs = Int32.Parse(Player1Legs.Content.ToString());
legs++;
Player1Legs.Content = legs.ToString();
if (legs == 3)
{
//increas sets
//if sets == 3, end game and shut down app
}
//Application.Current.Shutdown();
// Set flag so we do not keep looping through game over code
isGameOver = true;
}
Player1Score.Content = currentResult.ToString();
// Added this check here because I'm assuming you would want Player1Score to be reflected first
// before exiting the loop.
if (isGameOver)
{
// game is over, we do not want to keep showing the Game Over message box
break;
}
}
else
{
//the same for Player2
Player1Turn = true;
}
}
}
}
}
}
Those styles in the xaml are not defined anywhere you should remove them until you implement the styles. I removed them when compiling as they are not defined.
<Window x:Class="Scoreboard.MainScoreBoard"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Scoreboard"
Title="DartsLeague" Height="720" Width="1280">
<Grid x:Name="MainGrid" Background="DarkGreen">
<StackPanel x:Name="Player1" HorizontalAlignment="Left" VerticalAlignment="Stretch">
<Label x:Name="Player1Name"/>
<Label x:Name="Player1Score">501</Label>
<ScrollViewer x:Name="Player1Throws" Height="380" FlowDirection="RightToLeft">
</ScrollViewer>
</StackPanel>
<StackPanel x:Name="Player2" HorizontalAlignment="Right" VerticalAlignment="Stretch">
<Label x:Name="Player2Name">Player 2</Label>
<Label x:Name="Player2Score">501</Label>
<ScrollViewer x:Name="Player2Throws" Height="380">
</ScrollViewer>
</StackPanel>
<StackPanel x:Name="Input" HorizontalAlignment="Center" VerticalAlignment="Top">
<TextBox x:Name="CurrentNumber" MaxLength="3" Width="160" Margin="20" FontSize="30" TextChanged="TextBox_TextChanged"></TextBox>
<Button x:Name="SubmitButton" IsDefault="True" Click="Button_Click">Запиши резултат</Button>
<Button x:Name="CancelButton">Върни назад</Button>
</StackPanel>
<Grid x:Name="StatsGrid" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="60" Width="560">
<StackPanel x:Name="Player1Stats" HorizontalAlignment="Left" VerticalAlignment="Stretch">
<Label x:Name="Player1Legs">0</Label>
<Label x:Name="Player1Sets" />
<Label x:Name="Player1Avr" />
</StackPanel>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Stretch">
<Label>LEGS</Label>
<Label>SETS</Label>
<Label>3 DART AVERAGE</Label>
</StackPanel>
<StackPanel x:Name="Player2Stats" HorizontalAlignment="Right" VerticalAlignment="Stretch">
<Label x:Name="Player2Legs"/>
<Label x:Name="Player2Sets"/>
<Label x:Name="Player2Avr" />
</StackPanel>
</Grid>
</Grid>

A WrapPanel in which selection divides the panel to show a detail view?

Scrolling is horizontal
Touch-driven.
Items flow down, then into the next column
Touching an item would scroll the panel to a set point so the detail view would always be in the same spot.
The next column over would "break off" and animate to the right, to reveal a details pane in the context of the selected item.
Touching any visible item (in a different column) would "close" the revealed detail, then animate the new selected item to the left static point and again cut the next column away to reveal the detail. Touching any visible item in the same column would just do a fade-out-in animation.
Here are some simple mocks:
90% of this is simple to me, but the process to create a wrap panel which can "separate" itself to reveal an item is eluding me in a big way. Any advice would be appreciated.
One of the solutions is :
You can separate buttons inside wrap panel(which is inside grid) by changing margin of some of them (of course you need to change size of window as well if you want to keep buttons size and avoid moving them to next line).
For example if you have 4 columns and three rows of buttons named button 1,2,3 etc...
when button from first column is clicked buttons 2,6,10 getting :
new thickness(space,0,0,0);
This moves all the buttons on the right by value of variable space;
And then
window.width += space;
And then textBox which is children of the grid is to locate in suitable place with width space.
On undo
new thickness(0,0,0,0);
window.width -= space;
It worked for me well but i am curious for other solutions.
This sounded like a fun task, so I decided to implement something along the lines of what you wanted. I thought I could share it so you can improve it or use it to your liking. First of, this task is beyond my skills of implementing in only XAML. I'm not saying it can't be done, just that it's a tad difficult. Instead, I implemented my own Panel type (called GapPanel). This might sound even worse, but there's a few nice things about it nonetheless, like the possibility of implementing RoutedEvents to respond to in XAML for animations.
So, here's the load of code. First the XAML
<Window x:Class="SlidingWrapPanel.SecondAttempt"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SlidingWrapPanel"
Title="Wrapped items with details pane" Height="250" Width="600">
<Window.Resources>
<ControlTemplate TargetType="Button" x:Key="ItemButtonTemplate">
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="ButtonBorder"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="#999999" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="ButtonBorder"
Storyboard.TargetProperty="(Border.Background).(SolidColorBrush.Color)"
To="#3e3e3e" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
<Border x:Name="ButtonBorder" Background="#3e3e3e" BorderBrush="#222" BorderThickness="1">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{TemplateBinding Content}" Margin="0" />
</Border>
</ControlTemplate>
<Style x:Key="ItemGridStyle" TargetType="{x:Type Grid}">
<Setter Property="Background" Value="#3e3e3e"/>
<Setter Property="Width" Value="150"/>
<Setter Property="Height" Value="100"/>
<Setter Property="Margin" Value="1"/>
</Style>
<Style x:Key="ItemButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<Binding Source="{StaticResource ItemButtonTemplate}"/>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="DetailsGridStyle" TargetType="{x:Type Grid}">
<Setter Property="Background" Value="#3e3e3e"/>
<Setter Property="Width" Value="160"/>
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="-160" />
</Setter.Value>
</Setter>
</Style>
<Style x:Key="DetailsTextStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontFamily" Value="Segoe WP Light"/>
<Setter Property="Margin" Value="15"/>
</Style>
<Storyboard x:Key="ExpandColumnAnimation">
<DoubleAnimation Storyboard.TargetProperty="GapWidth" Storyboard.TargetName="ItemsPanel"
From="0" To="{Binding ActualWidth, ElementName=DetailsPanel}" Duration="0:0:0.75">
<DoubleAnimation.EasingFunction>
<QuinticEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"
Storyboard.TargetName="DetailsPanel">
<DiscreteDoubleKeyFrame KeyTime="0" Value="{Binding GapX, ElementName=ItemsPanel}"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="CollapseColumnAnimation">
<DoubleAnimation Storyboard.TargetProperty="GapWidth" Storyboard.TargetName="ItemsPanel"
To="0" Duration="0:0:0.5">
<DoubleAnimation.EasingFunction>
<QuinticEase EasingMode="EaseIn"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"
Storyboard.TargetName="DetailsPanel">
<DiscreteDoubleKeyFrame KeyTime="0:0:0.5" Value="-160"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Grid>
<Grid>
<Grid x:Name="DetailsPanel" Style="{StaticResource DetailsGridStyle}">
<ScrollViewer>
<TextBlock Style="{StaticResource DetailsTextStyle}">
<Run Text="Details" FontSize="18"/>
<LineBreak />
<Run Text="Some text"/>
</TextBlock>
</ScrollViewer>
</Grid>
</Grid>
<local:GapPanel x:Name="ItemsPanel">
<local:GapPanel.Triggers>
<EventTrigger RoutedEvent="local:GapPanel.ColumnChanged">
<BeginStoryboard Storyboard="{StaticResource ExpandColumnAnimation}"/>
</EventTrigger>
<EventTrigger RoutedEvent="local:GapPanel.CloseGap">
<BeginStoryboard Storyboard="{StaticResource CollapseColumnAnimation}"/>
</EventTrigger>
</local:GapPanel.Triggers>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 1" />
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 2" />
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 3" />
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 4"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 5"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 6"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 7"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 8"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 9"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 10"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 11"/>
</Grid>
<Grid Style="{StaticResource ItemGridStyle}">
<Button Style="{StaticResource ItemButtonStyle}" Content="Item 12"/>
</Grid>
</local:GapPanel>
</Grid>
</Window>
And the (abomination) of a panel..
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SlidingWrapPanel {
public class GapPanel : Panel, INotifyPropertyChanged {
private readonly IDictionary<UIElement, int> columns;
private readonly IDictionary<int, double> gapCoordinates;
private object opened;
public static readonly DependencyProperty GapColumnProperty =
DependencyProperty.Register("GapColumn", typeof(int), typeof(GapPanel), new FrameworkPropertyMetadata(default(int), FrameworkPropertyMetadataOptions.AffectsRender, columnChanged));
public static readonly DependencyProperty GapWidthProperty =
DependencyProperty.Register("GapWidth", typeof(double), typeof(GapPanel), new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly RoutedEvent ColumnChangedEvent;
public static readonly RoutedEvent CloseGapEvent;
static GapPanel() {
ColumnChangedEvent = EventManager.RegisterRoutedEvent("ColumnChanged", RoutingStrategy.Bubble, typeof(RoutedEvent), typeof(GapPanel));
CloseGapEvent = EventManager.RegisterRoutedEvent("CloseGap", RoutingStrategy.Bubble, typeof(RoutedEvent), typeof(GapPanel));
}
public GapPanel() {
columns = new Dictionary<UIElement, int>();
gapCoordinates = new Dictionary<int, double>();
GapWidth = 0;
GapColumn = -1;
}
public int GapColumn {
get { return (int)GetValue(GapColumnProperty); }
set { SetValue(GapColumnProperty, value); }
}
public double GapWidth {
get { return (double)GetValue(GapWidthProperty); }
set { SetValue(GapWidthProperty, value); }
}
public double GapX {
get {
double value;
gapCoordinates.TryGetValue(GapColumn, out value);
return value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public event RoutedEventHandler ColumnChanged {
add { AddHandler(ColumnChangedEvent, value); }
remove { RemoveHandler(ColumnChangedEvent, value); }
}
public event RoutedEventHandler CloseGap {
add { AddHandler(CloseGapEvent, value); }
remove { RemoveHandler(CloseGapEvent, value); }
}
protected override Size ArrangeOverride(Size finalSize) {
Point location = new Point();
double position = 0;
double columnWidth = 0;
int col = 0;
foreach (UIElement child in Children) {
columnWidth = Math.Max(columnWidth, child.DesiredSize.Width);
position += child.DesiredSize.Height;
if (position > finalSize.Height && columnWidth > 0) {
location.X += columnWidth;
if (col == GapColumn) {
location.X += GapWidth;
}
++col;
columnWidth = 0;
position = child.DesiredSize.Height;
location.Y = 0;
}
columns[child] = col;
child.Arrange(new Rect(location, child.DesiredSize));
location.Y = position;
}
return finalSize;
}
protected override Size MeasureOverride(Size availableSize) {
double width = 0, height = 0;
double position = 0;
double columnWidth = 0;
int col = 0;
foreach (UIElement child in Children) {
child.Measure(availableSize);
columnWidth = Math.Max(columnWidth, child.DesiredSize.Width);
position += child.DesiredSize.Height;
if (position > availableSize.Height && columnWidth > 0) {
width += columnWidth;
++col;
columnWidth = child.DesiredSize.Width;
position = child.DesiredSize.Height;
height = Math.Max(height, child.DesiredSize.Height);
}
gapCoordinates[col] = width + columnWidth;
}
return new Size(width + GapWidth, height);
}
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved) {
base.OnVisualChildrenChanged(visualAdded, visualRemoved);
UIElement element = visualAdded as UIElement;
if (element != null) {
element.PreviewMouseLeftButtonDown += expandAtVisual;
}
element = visualRemoved as UIElement;
if (element != null) {
element.PreviewMouseLeftButtonDown -= expandAtVisual;
}
}
private void expandAtVisual(object sender, MouseButtonEventArgs e) {
// find element column
int column = columns[(UIElement)sender];
GapWidth = 0;
GapColumn = column;
if (opened == sender) {
RaiseEvent(new RoutedEventArgs(CloseGapEvent, this));
}
opened = sender;
}
private void onPropertyChanged(string propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private static void columnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
((GapPanel)d).onPropertyChanged("GapX");
((GapPanel)d).RaiseEvent(new RoutedEventArgs(ColumnChangedEvent, d));
}
}
}
Leave a comment if there's anything you feel I need to explain.
Also, I can't help promoting the book "WPF4 Unleashed" by Adam Nathan. Much if not everything I managed to do above is explained in great detail in this book, so it's a great resource for anyone wanting to learn more about WPF.

Display pictures as hide or visible

Goal:
When user start typing text or characters in the textbox txtSearch the picture picEnlarger will be hidden and be replaced by picture picXmark.
In default, the picEnlarger will always display until input data will be applied in the textbox txtSearch. In order word, no data in textbox then display picEnlarger and hide picXmark.
Problem:
Having problem to display the picture picXmark and hide the picture picEnlarger when the user start typing characters in the textbox named txtSearch.
When I tried coding in C# to gain this functionality no effect would occur in the run time.
I tried using the code:
picEnlarger = new Image();
picXmark = new Image();
But no effect has happened.
XAML code from Stock.xaml:
<Canvas Height="39.667" Margin="8,0,215.397,0" VerticalAlignment="Top">
<Button x:Name="btnNewProduct" Content="New" Width="75" Click="btnNewProduct_Click" Height="20.277" RenderTransformOrigin="0.667,1.726" d:LayoutOverrides="VerticalAlignment, Margin" Canvas.Left="0.001" Canvas.Top="18.723" />
<Button x:Name="btnAddDelivery" Content="Add quantity" Width="75" Click="btnAddDelivery_Click" d:LayoutOverrides="VerticalAlignment, Margin" Height="20.277" Canvas.Left="79.001" Canvas.Top="18.723" />
<Button x:Name="btnDeleteProduct" Content="Delete" Width="75" RenderTransformOrigin="0.107,1.843" Click="btnDeleteProduct_Click" Height="20.277" Canvas.Left="158.001" d:LayoutOverrides="HorizontalAlignment, VerticalAlignment, Width" Canvas.Top="18.723" />
<Button x:Name="btnEdit" Content="Edit" Canvas.Left="237.001" Width="75" Canvas.Top="18.723" Click="btnEdit_Click" />
<TextBox Name="txtSearch" Canvas.Left="391.36" TextWrapping="Wrap" Canvas.Top="18.723" Width="143.243" TextChanged="txtSearch_TextChanged" Text=" Search article" PreviewMouseLeftButtonDown="txtSearch_PreviewMouseLeftButtonDown" TextInput="txtSearch_TextInput">
</TextBox>
<Label Content="Advanced Search" HorizontalAlignment="Left" Canvas.Left="444.289"/>
<Image x:Name="picXmark" Height="8" Source="/MediaStore;component/Bilder/search_xmark.gif" Stretch="Fill" Width="8" Canvas.Left="519.853" Canvas.Top="24.167" Visibility="Hidden" />
<Image x:Name="picEnlarger" Height="14" Canvas.Left="513.75" Source="/MediaStore;component/Bilder/search_enlarger2.gif" Stretch="Fill" Canvas.Top="21.527" Width="14" Visibility="Hidden" ImageFailed="picEnlarger_ImageFailed" />
</Canvas>
Class Stock
private void txtSearch_TextChanged(object sender, TextChangedEventArgs e)
{
picEnlarger = new Image();
picXmark = new Image();
if (txtSearch.Text != "")
{
picEnlarger.Visibility = Visibility.Collapsed;
picXmark.Visibility = Visibility.Visible;
RegularSearch myRegularSearch = new RegularSearch();
myRegularSearch.Test(txtSearch.Text);
}
else
{
picEnlarger.Visibility = Visibility.Visible;
picXmark.Visibility = Visibility.Hidden;
}
}
private void txtSearch_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
txtSearch.Text = "";
}
In theory you should be able to just use triggers for that, e.g.
<TextBox Name="txtSearch" />
<Image Name="ImageOne">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=txtSearch}"
Value="">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Image Name="ImageOne">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=txtSearch}"
Value="">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
When text is entered one image will become visible while the other one will be hidden.

Categories