Does anyone encounter annoyed issue when update background color of cell in xamarin form?
I have notification list, when user clicks on item, it's marked as read then background color will be updated.
It works perfectly on android but iOS.
Most of the time the color is changed back to origin.
In the example, cell background is blue if notification is read.
When user clicks on cell, cell background should be red, but it's not changed most of time.
Notification Model
public class Notification : ObservableObject
{
private string _message;
private bool _isRead;
public string Message
{
get => _message;
set => SetProperty(ref _message, value);
}
public bool IsRead
{
get => _isRead;
set => SetProperty(ref _isRead, value);
}
}
ViewModel
public class MainPageViewModel : BaseViewModel
{
private ObservableCollection<Notification> _notifications;
public ObservableCollection<Notification> Notifications
{
get => _notifications;
set => SetProperty(ref _notifications, value);
}
public ICommand TappedCommand => new Command((o => OnTapped(o)));
public MainPageViewModel()
{
_notifications = new ObservableCollection<Notification>()
{
new Notification()
{
IsRead = false,
Message = "First notification"
},
new Notification()
{
IsRead = false,
Message = "Second notification"
},
new Notification()
{
IsRead = false,
Message = "Third notification"
},
new Notification()
{
IsRead = false,
Message = "Fourth notification"
}
};
}
private void OnTapped(object o)
{
if(!(o is Notification noti)) return;
noti.IsRead = true;
}
}
Page:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:NotificationTest"
x:Class="NotificationTest.MainPage">
<ContentPage.Content>
<ListView x:Name="NotiList" ItemsSource="{Binding Notifications}" ItemTapped="OnTapped" ItemSelected="OnSelected" CachingStrategy="RecycleElement">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid BackgroundColor="{Binding IsRead,Converter={StaticResource ReadToColorConverter}}">
<Label Text="{Binding Message}"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
Page code behind ( just work around to hide selected line color in listview
public partial class MainPage : ContentPage
{
private MainPageViewModel _vm;
public MainPage()
{
InitializeComponent();
_vm = new MainPageViewModel();
BindingContext = _vm;
}
private void OnTapped(object sender, ItemTappedEventArgs e)
{
_vm.TappedCommand.Execute(e.Item);
}
private void OnSelected(object sender, SelectedItemChangedEventArgs e)
{
NotiList.SelectedItem = null;
}
}
The problem was caused by the default behavior when selecting cell in iOS.
The default selected color will cover the red color , so it didn't get working.
Refer to my recent answer Here
Test
I hope I can explain my problem properly.
I have a datagrid bound to an ObservableCollection object, and a TextBox bound to the selected item of my datagrid.
When I programmatically modify the SelectedItem property value (Name), my TextBox text value is not updated !
here is my design code:
<DataGrid Name="grid" HorizontalAlignment="Left" Margin="119,28,0,0" VerticalAlignment="Top" Height="237" Width="200" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="nom" Binding="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</DataGrid.Columns>
</DataGrid>
<TextBox Name="textbox" Text="{Binding ElementName=grid, Path=SelectedItem.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="18" Margin="119,276,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="200"/>
<Button Content="Button" HorizontalAlignment="Left" Margin="392,54,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
and here is my Code Behind:
ObservableCollection<Element> obs;
class Element
{
public string Name { get; set; }
public Element(string name) { Name = name; }
}
public MainWindow()
{
InitializeComponent();
obs = new ObservableCollection<Element>() { new Element("element2"), new Element("element2"), new Element("element3")};
grid.ItemsSource = obs;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var sel = grid.SelectedItem as Element;
sel.Name = "something";
grid.Items.Refresh(); //this updates the selected element to "something" but does nothing to the textbox
}
Problem solved.
I had to fire PropertyChanged event whenever a modification is made to the model.
here my new code:
class Element : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Name"));
}
}
public Element(string name) { Name = name; }
}
I created a control in Xamarin forms that flips views(Like a card style). both these views have some sort of input (EX: List of buttons) that if you interact with will flip the "Card" control to the next view. I was able to get this working with Android, but when I test with IOS the controls seems to be disabled and I am not able to hit any events. Now I did solve a similar problem before by using the e.NativeView.UserInteractionEnabled property. The only issue is that this property can only be use when initializing the view, I want to be able to use something similar that is more dynamic.
A little progress on the IOS issue.
<?xml version="1.0" encoding="utf-8" ?>
<TemplatedView xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="minto.qm.mobile.Views.Controls.PanelView">
<TemplatedView.ControlTemplate>
<ControlTemplate>
<AbsoluteLayout>
//the last item in this list will be the dominant control on runtime
//the other views input will not work
<ContentView Content="{TemplateBinding BackContent}" AbsoluteLayout.LayoutBounds="1,1,1,1" AbsoluteLayout.LayoutFlags="WidthProportional,HeightProportional,PositionProportional" AnchorX="0.5" AnchorY="0.5"></ContentView>
<ContentView Content="{TemplateBinding FrontContent}" AbsoluteLayout.LayoutBounds="1,1,1,1" AbsoluteLayout.LayoutFlags="WidthProportional,HeightProportional,PositionProportional" AnchorX="0.5" AnchorY="0.5"></ContentView>
</AbsoluteLayout>
</ControlTemplate>
</TemplatedView.ControlTemplate>
</TemplatedView>
Does anyone know of any functionality like this?
https://forums.xamarin.com/discussion/81114/how-to-create-a-custom-controls-with-multiple-view-on-top-of-each-other
update:
here is the behind code for the control:
public partial class PanelView : TemplatedView
{
public event EventHandler FrontContentAppeared;
public event EventHandler BackContentAppeared;
public static readonly BindableProperty FrontContentProperty = BindableProperty.Create(nameof(FrontContent), typeof(View), typeof(PanelView), defaultValue: null, propertyChanged: OnFrontContentChanged);
public static readonly BindableProperty BackContentProperty = BindableProperty.Create(nameof(BackContent), typeof(View), typeof(PanelView), defaultValue: null, propertyChanged: OnBackContentChanged);
public static readonly BindableProperty SwitchViewProperty = BindableProperty.Create(nameof(SwitchView), typeof(bool), typeof(PanelView), defaultValue: false, propertyChanged: OnSwitchViewChanged);
private bool _isFrontView = true;
public PanelView()
{
InitializeComponent();
}
private async void SwitchCurrentView()
{
if (_isFrontView)
{
BackContent.IsVisible = true;
//BackContent.InputTransparent = true;
FrontContent.Unfocus();
BackContent.Focus();
await Task.WhenAll(
FrontContent.FadeTo(0, 500, Easing.Linear),
BackContent.FadeTo(1, 500, Easing.Linear),
this.RotateYTo(GetRotation(0, 180), 250, Easing.Linear)
);
FrontContent.IsVisible = false;
//FrontContent.InputTransparent = false;
BackContentAppeared?.Invoke(this, EventArgs.Empty);
}
else
{
FrontContent.IsVisible = true;
//FrontContent.InputTransparent = true;
FrontContent.Focus();
BackContent.Unfocus();
await Task.WhenAll(
FrontContent.FadeTo(1, 500, Easing.Linear),
BackContent.FadeTo(0, 500, Easing.Linear),
this.RotateYTo(GetRotation(180, 180), 250, Easing.Linear)
);
BackContent.IsVisible = false;
//BackContent.InputTransparent = false;
FrontContentAppeared?.Invoke(this, EventArgs.Empty);
}
_isFrontView = !_isFrontView;
SwitchView = false;
}
private static void OnFrontContentChanged(BindableObject bindable, object oldValue, object newValue)
{
var self = (PanelView)bindable;
self.SetFrontContentView((View)newValue);
}
private static void OnBackContentChanged(BindableObject bindable, object oldValue, object newValue)
{
var self = (PanelView)bindable;
self.SetBackContentView((View)newValue);
}
private static void OnSwitchViewChanged(BindableObject bindable, object oldValue, object newValue)
{
var self = (PanelView)bindable;
self.SwitchView = (bool)newValue;
if (self.SwitchView)
{
self.SwitchCurrentView();
}
}
private void SetFrontContentView(View view)
{
FrontContent = view;
if (!_isFrontView)
{
FrontContent.IsVisible = false;
view.FadeTo(0, 1, Easing.Linear);
}
}
private void SetBackContentView(View view)
{
view.FadeTo(0, 1, Easing.Linear);
view.RotateYTo(180, 1, Easing.Linear);
BackContent = view;
if (_isFrontView)
{
BackContent.IsVisible = false;
}
}
private double GetRotation(double start, double amount)
{
var rotation = (start + amount) % 360;
return rotation;
}
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if (FrontContent != null)
{
SetInheritedBindingContext(FrontContent, BindingContext);
}
if (BackContent != null)
{
SetInheritedBindingContext(BackContent, BindingContext);
}
}
public View FrontContent
{
get { return (View)GetValue(FrontContentProperty); }
set { SetValue(FrontContentProperty, value); }
}
public View BackContent
{
get { return (View)GetValue(BackContentProperty); }
set { SetValue(BackContentProperty, value); }
}
public bool SwitchView
{
get { return (bool)GetValue(SwitchViewProperty); }
set { SetValue(SwitchViewProperty, value); }
}
}
the animation runs when the bindable bool variable SwitchView changes
private void OnRoomTypeTapped(object sender, GliderItemTappedEventArgs e)
{
if (e.IsSelected && e.IsSelected == e.LastSelection)
{
PanelView.SwitchView = true;
}
}
UPDATE:
Xaml Page example. note: (Base:ExtendedPage is a custom contentpage) and (controls:Glider is are custom view with list of buttons on them)
<?xml version="1.0" encoding="utf-8" ?>
<base:ExtendedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:minto.qm.mobile.Views.Controls;assembly=minto.qm.mobile"
xmlns:base="clr-namespace:minto.qm.mobile.Views.Pages.Base;assembly=minto.qm.mobile"
x:Class="minto.qm.mobile.Views.Pages.RoomPage"
Navigation="{Binding Navigation, Mode=OneWayToSource}"
Title="Room">
<base:ExtendedPage.Content>
<ScrollView>
<StackLayout Spacing="0">
<ContentView BackgroundColor="#3498DB" Padding="10">
<Label Text="{Binding SelectedRoomName}" FontAttributes="Bold" HorizontalTextAlignment="Center" VerticalTextAlignment="Center"></Label>
</ContentView>
<StackLayout Spacing="5" Padding="10, 20, 10, 10">
<controls:PanelView x:Name="PanelView">
<controls:PanelView.FrontContent>
<controls:Glider ItemsSource="{Binding RoomTypes}" DisplayProperty="Name" SelectedIndex="0" ItemSelected="OnRoomTypeSelected" ItemTapped="OnRoomTypeTapped" Orientation="Vertical" Lines="3" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"></controls:Glider>
</controls:PanelView.FrontContent>
<controls:PanelView.BackContent>
<controls:Glider ItemsSource="{Binding Rooms}" DisplayProperty="Name" SelectedIndex="0" ItemSelected="OnRoomSelected" ItemTapped="OnRoomTapped" Orientation="Horizontal" Lines="5" VerticalOptions="FillAndExpand" HorizontalOptions="FillAndExpand"></controls:Glider>
</controls:PanelView.BackContent>
</controls:PanelView>
<StackLayout Orientation="Horizontal">
<Label Text="Room annotation:" FontAttributes="Bold" Margin="5, 0, 0, 0" VerticalTextAlignment="Center"></Label>
<Button Text="<" FontAttributes="Bold" HorizontalOptions="EndAndExpand" VerticalOptions="Center" BackgroundColor="Transparent" WidthRequest="50" HeightRequest="50" Clicked="OnExpandButtonClicked" FontSize="Medium" AnchorX="0.5" AnchorY="0.5"></Button>
</StackLayout>
<Entry x:Name="AnnotationEditor" HorizontalOptions="FillAndExpand" BackgroundColor="#4D4D4D" TextChanged="Entry_OnTextChanged"/>
<Button Text="Next" Command="{Binding NextPageCommand}" HorizontalOptions="FillAndExpand" />
</StackLayout>
</StackLayout>
</ScrollView>
</base:ExtendedPage.Content>
<base:ExtendedPage.Overlay>
<controls:Tombstone Token="{Binding DeficiencyToken}" BackgroundColor="#444444"></controls:Tombstone>
</base:ExtendedPage.Overlay>
<base:ExtendedPage.ToolbarItems>
<ToolbarItem Text="Details" Order="Primary" Priority="0" Clicked="DetailsClicked"></ToolbarItem>
</base:ExtendedPage.ToolbarItems>
</base:ExtendedPage>
public partial class RoomPage : ExtendedPage
{
public ViewModels.Pages.RoomPage ViewModel => _vm ?? (_vm = BindingContext as ViewModels.Pages.RoomPage);
private ViewModels.Pages.RoomPage _vm;
private bool _buttonExpanded;
public RoomPage()
{
InitializeComponent();
BindingContext = new ViewModels.Pages.RoomPage();
}
protected override void OnAppearing()
{
base.OnAppearing();
ViewModel.OnAppearing();
HandleCaching();
}
private async void HandleCaching()
{
await Task.Run(() =>
{
var pageCache = Services.Caches.Pages.GetInstance();
pageCache.Preload(nameof(InspectionGalleryPage), new InspectionGalleryPage());
});
}
private void DetailsClicked(object sender, EventArgs e)
{
ShowOverlay = !ShowOverlay;
}
private void Entry_OnTextChanged(object sender, TextChangedEventArgs e)
{
ViewModel.RoomAnnotations = e.NewTextValue;
}
private void OnRoomTypeSelected(object sender, GliderItemSelectedEventArgs e)
{
ViewModel.SelectedRoomTypeName = e.Item.ToString();
}
private void OnRoomTypeTapped(object sender, GliderItemTappedEventArgs e)
{
if (e.IsSelected && e.IsSelected == e.LastSelection)
{
PanelView.SwitchView = true;
}
}
private void OnRoomSelected(object sender, GliderItemSelectedEventArgs e)
{
ViewModel.SelectedRoomName = e.Item.ToString();
}
private void OnRoomTapped(object sender, GliderItemTappedEventArgs e)
{
if (e.IsSelected && e.IsSelected == e.LastSelection)
{
PanelView.SwitchView = true;
}
}
private void AnimateHeight(View view, double end)
{
view.Animate("Expander", value => view.HeightRequest = value, view.Height, end, 2, 250, Easing.Linear);
}
private void OnExpandButtonClicked(object sender, EventArgs e)
{
var button = (Button) sender;
if (_buttonExpanded)
{
button.RotateTo(0, 250, Easing.Linear);
AnimateHeight(AnnotationEditor, 45.5);
}
else
{
button.RotateTo(-90, 250, Easing.Linear);
AnimateHeight(AnnotationEditor, 300);
}
_buttonExpanded = !_buttonExpanded;
}
}
As I said "something" is covering your button. Here is the problematic declaration
<AbsoluteLayout>
<ContentView Content="{TemplateBinding BackContent}" BackgroundColor="Transparent" AbsoluteLayout.LayoutBounds="1,1,1,1" AbsoluteLayout.LayoutFlags="WidthProportional,HeightProportional,PositionProportional" AnchorX="0.5" AnchorY="0.5"></ContentView>
<ContentView Content="{TemplateBinding FrontContent}" BackgroundColor="Transparent" AbsoluteLayout.LayoutBounds="1,1,1,1" AbsoluteLayout.LayoutFlags="WidthProportional,HeightProportional,PositionProportional" AnchorX="0.5" AnchorY="0.5"></ContentView>
</AbsoluteLayout>
Your FrontContent ContentView covers BackContent ContentView. When you rotate and hide content you actually working with buttons not views. I found this design a little more complicated than it should be but regardless that was not the question. Below is the solution. When you hide you content (meaning button) also hide your Parent view and show it when you show the button.
private async void SwitchCurrentView()
{
if (_isFrontView)
{
BackContent.IsVisible = true;
((ContentView)BackContent.Parent).IsVisible = true;//************************
FrontContent.Unfocus();
BackContent.Focus();
await Task.WhenAll(
FrontContent.FadeTo(0, 500, Easing.Linear),
BackContent.FadeTo(1, 500, Easing.Linear),
this.RotateYTo(GetRotation(0, 180), 250, Easing.Linear)
);
FrontContent.IsVisible = false;
((ContentView)FrontContent.Parent).IsVisible = false;//******************
BackContentAppeared?.Invoke(this, EventArgs.Empty);
}
else
{
FrontContent.IsVisible = true;
((ContentView)FrontContent.Parent).IsVisible = true;
FrontContent.Focus();
BackContent.Unfocus();
await Task.WhenAll(
FrontContent.FadeTo(1, 500, Easing.Linear),
BackContent.FadeTo(0, 500, Easing.Linear),
this.RotateYTo(GetRotation(180, 180), 250, Easing.Linear)
);
BackContent.IsVisible = false;
((ContentView)BackContent.Parent).IsVisible = false;
FrontContentAppeared?.Invoke(this, EventArgs.Empty);
}
_isFrontView = !_isFrontView;
SwitchView = false;
}
I want to use twoway binding in my customcontrol. It's codes;
CustomControl;
<Grid>
<PasswordBox x:Name="passwordB" GotFocus="PasswordBox_GotFocus" LostFocus="PasswordBox_LostFocus" PasswordChanged="passwordB_PasswordChanged" Style="{StaticResource AkbankControlStyleWatermarkPasswordBoxLoginFormInputPasswordBox}"></PasswordBox>
<TextBlock x:Name="lblWaterMark" MouseLeftButtonDown="lblWaterMark_Tapped" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="20,10,20,10" Opacity="0.8" FontFamily="Segoe UI" FontSize="16" Foreground="#FF8E8E8E" FontWeight="SemiBold"></TextBlock>
</Grid>
It's name is WatermarkPasswordTextBox :)
DependencyProperty;
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register(
"PassText",
typeof(string),
typeof(WatermarkPasswordTextBox),
new PropertyMetadata(""));
Properties;
private string _passText = "";
public string PassText
{
get
{
if (passwordB != null)
{
_passText = passwordB.Password;
return _passText;
}
else
{
return String.Empty;
}
}
set
{
if (passwordB != null)
{
SetProperty<string>(ref _passText, value, "PassText");
passwordB.Password = _passText;
passwordB_PasswordChanged(passwordB, null);
}
else
{
SetProperty<string>(ref _passText, value, "PassText");
}
}
}
OnApplyTemplate ;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.SetBinding(
WatermarkPasswordTextBox.PasswordProperty,
new Binding
{
Path = new PropertyPath("PassText"),
Mode = BindingMode.TwoWay,
Source = this
});
}
My Xaml;
<CustomControls:WatermarkPasswordTextBox
PassText="{Binding Password,Mode=TwoWay}"
Padding="5"
x:Name="CustomerPasswordTextBox"
x:FieldModifier="public"
LenghtMax="6"
Watermark="{Binding LocalizedResources.PasswordWatermarkWatermark,Source={StaticResource LocalizedStrings}}"
RelayedKeyUp="CustomerPasswordTextBox_KeyUp"
HorizontalContentAlignment="Left"/>
Error Code;
System.ArgumentException: Object of type 'System.Windows.Data.Binding' cannot be converted to type 'System.String'.
This code is giving runtime error.
Thanks.
i have an combox control defined with events in my mainpage.xaml
<Grid x:Name="LayoutRoot">
<ComboBox SelectionChanged="ComboBox_SelectionChanged"></ComboBox>
</Grid>
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
now how do we defined events for combox control in mvvm model .
and how do we bind the collection list to combo box. i am using SL 3
thanks
prince
In your xaml, you can bind the ItemSource and SelectedItem as shown below:
MainPage.xaml
<UserControl x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:App1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.DataContext>
<local:MainPage_ViewModel/>
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White">
<ComboBox ItemsSource="{Binding MyItems}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" SelectionChanged="ComboBox_SelectionChanged" Height="30" Width="100"/>
</Grid>
In the MainPage.xaml.cs, your Selection changed method could just call the method on your ViewModel since you are using SL3:
MainPage.xaml.cs
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private MainPage_ViewModel viewModel
{
get { return this.DataContext as MainPage_ViewModel; }
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.viewModel.SelectionChanged();
}
}
Your ViewModel would have the MyItems collection and the SelectedItem to bind to:
MainPage_ViewModel.cs
public class MainPage_ViewModel : INotifyPropertyChanged
{
public ObservableCollection<string> MyItems
{
get { return myItems; }
set { myItems = value; }
}
private ObservableCollection<string> myItems = new ObservableCollection<string>() { "One", "Two", "Three" };
public string SelectedItem
{
get { return selectedItem; }
set { selectedItem = value; }
}
private string selectedItem = string.Empty;
public void SelectionChanged()
{
//Perform logic that needs to happen when selection changes
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Depending on what you were using your SelectionChanged method for, you may no longer need it since this would bind the SelectedItem to the ViewModel.
Hope this helps!