Xamarin Forms CollectionView: Cannot give SelectedItem Transparent background - xamarin

I am using a CollectionView and when a user selects an item, I don't want the SelectedItem to show background color at all. I have tried to achieve this effect by setting the BackgroundColor property to transparent with the VisualStateManager per the instructions in Xamarin's documentation. However, rather than the Item's Background being invisible it just becomes grayed-out when selected. The code works. If I set it to red, I see red. But I can't get the background to go away altogether.
This is happening in iOS.
Can anyone tell me how to do this?
Here's my code:
<Style TargetType="ContentView">
<Setter Property="VisualStateManager.VisualStateGroups">
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Selected">
<VisualState.Setters>
<Setter Property="BackgroundColor"
Value="Transparent" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</Setter>
</Style>
<CollectionView Grid.Row="0" ItemsSource="{Binding Lessons}" BackgroundColor="Transparent"
SelectedItem="{Binding SelectedLesson, Mode=TwoWay}" HorizontalOptions="FillAndExpand"
SelectionMode="Single"
cal:Message.Attach="[Event SelectionChanged] = [Action ActivateLesson]">
<CollectionView.ItemTemplate >
<DataTemplate x:DataType="engineVm:LessonViewModel">
<ContentView BackgroundColor="Transparent" cal:View.Model="{Binding}" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Margin="0, 0, 0, 20" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>

This can be accomplished by using Custom Renderer
using UIKit;
using Xamarin.Forms.Platform.iOS;
using Xamarin.Forms;
using App7.iOS;
[assembly:ExportRenderer(typeof(ViewCell),typeof(MyViewCellRenderer))]
namespace App7.iOS
{
public class MyViewCellRenderer: ViewCellRenderer
{
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
var cell= base.GetCell(item, reusableCell, tv);
cell.SelectedBackgroundView = new UIView
{
BackgroundColor = Color.Transparent.ToUIColor(),
};
cell.SelectionStyle = UITableViewCellSelectionStyle.None;
return cell;
}
}
}
in xaml
<CollectionView.ItemTemplate >
<DataTemplate >
<ViewCell>
<ContentView BackgroundColor="Transparent" HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand" Margin="0, 0, 0, 20" />
</ViewCell>
</DataTemplate>
</CollectionView.ItemTemplate>
Update
You could use the plugin FlowListView from Nuget . It provides the similar function like CollectionView .
And you could create a custom renderers for FlowListViewInternalCell in platforms specific projects which disable ListView row highlighting.
iOS
using System;
using DLToolkit.Forms.Controls;
using DLToolkitControlsSamples.iOS;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
[assembly: ExportRenderer(typeof(FlowListViewInternalCell), typeof(FlowListViewInternalCellRenderer))]
namespace DLToolkitControlsSamples.iOS
{
// DISABLES FLOWLISTVIEW ROW HIGHLIGHT
public class FlowListViewInternalCellRenderer : ViewCellRenderer
{
public override UIKit.UITableViewCell GetCell(Xamarin.Forms.Cell item, UIKit.UITableViewCell reusableCell, UIKit.UITableView tv)
{
tv.AllowsSelection = false;
var cell = base.GetCell(item, reusableCell, tv);
cell.SelectionStyle = UITableViewCellSelectionStyle.None;
return cell;
}
}
}
Android
using System;
using DLToolkit.Forms.Controls;
using DLToolkitControlsSamples.Droid;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(FlowListViewInternalCell), typeof(FlowListViewInternalCellRenderer))]
namespace DLToolkitControlsSamples.Droid
{
// DISABLES FLOWLISTVIEW ROW HIGHLIGHT
public class FlowListViewInternalCellRenderer : ViewCellRenderer
{
protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView, Android.Views.ViewGroup parent, Android.Content.Context context)
{
var cell = base.GetCellCore(item, convertView, parent, context);
var listView = parent as Android.Widget.ListView;
if (listView != null)
{
listView.SetSelector(Android.Resource.Color.Transparent);
listView.CacheColorHint = Android.Graphics.Color.Transparent;
}
return cell;
}
}
}
For more details and usage of the plugin you could check https://github.com/daniel-luberda/DLToolkit.Forms.Controls/tree/master/FlowListView/

Related

Visual state manager does not work on a button in a collection view

I'm trying to change the background color or text color of a button in a collection view when clicked, this is what I have so far, but it is not working. I even tried to have the button as the root but the state still does not change
<CollectionView
ItemsSource="{Binding Suburbs}"
SelectionMode="None"
VerticalScrollBarVisibility="Never"
HorizontalScrollBarVisibility="Never">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Horizontal" ItemSpacing="5" />
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout
VerticalOptions="Start">
<Button
Padding="15"
CornerRadius="10"
BorderColor="Black"
BorderWidth="1"
TextColor="Black"
CommandParameter="{Binding .}"
Command="{Binding Source={x:Reference browse}, Path=BindingContext.CitySelectedCommand}"
BackgroundColor="Transparent"
Text="{Binding name}"
HeightRequest="30"
VerticalOptions="Start"
HorizontalOptions="FillAndExpand">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ColorStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Pressed">
<VisualState.Setters>
<Setter Property="TextColor" Value="Red"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Button>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</StackLayout>
``
Actually VisualStateGroup does work .
However the color only changes when you're pressing on the button , the color will change to origin color after you release the button .
Refer to Visual states in Xamarin.Forms.
If you want the button stays another color after clicking on it , you need to create a property in the model and bind it to the TextColor/BackgroundColor.
Xaml
TextColor="{Binding color}"
Command="{Binding command}"
Model
public class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string name { get; set; }
public Color _color;
public Color color {
get { return _color; }
set {
_color = value;
NotifyPropertyChanged();
}
}
public ICommand command { get; set; }
bool isClick = false;
public Model()
{
color = Color.Black;
command = new Command((obj)=> {
isClick = !isClick;
color = isClick ? Color.Red : Color.Black;
});
}
}

Xamarin Forms: System.NullReferenceException in AppDelegate

Hi tried to compile my app for the first time for IOS but I got this:
System.NullReferenceException: Object reference not set to an instance of an object
in the AppDelegate.
It stops in this line :return base.FinishedLaunching(app, options);
I'm relative new into xamarin so I am sorry for my unknowingness.
Here is my complete AppDelegate:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
using System.Globalization;
using System.IO;
using Flex;
using Foundation;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;
using System.Xml.Linq;
namespace dpsApp.iOS
{
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
global::Xamarin.Forms.Forms.Init();
Rg.Plugins.Popup.Popup.Init();
FlexButton.Init();
LoadApplication(new App());
return base.FinishedLaunching(app, options);
}
}
}
So here is my MainPage XAML:
`
<StackLayout WidthRequest="10000">
<StackLayout x:Name="LogoStack" BackgroundColor="White">
<Image x:Name="Image"
Source="a.png"
HeightRequest="120"
WidthRequest="120"
HorizontalOptions="Center">
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="EasterCount"/>
</Image.GestureRecognizers>
</Image>
</StackLayout>
<StackLayout x:Name="StackList" IsVisible="True" HeightRequest="3000">
<ListView x:Name="PageList"
HasUnevenRows="True"
ItemTapped="Link_ItemTapped"
HeightRequest="25"
BackgroundColor="White">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Orientation="Horizontal" Margin="0,0,0,0" BackgroundColor="#e7e7e7" HeightRequest="65" Padding="0">
<Grid x:Name="DeleteStack" VerticalOptions="CenterAndExpand" BackgroundColor="White" HorizontalOptions="FillAndExpand" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="50" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="28" />
<RowDefinition Height="22" />
<RowDefinition Height="1" />
</Grid.RowDefinitions>
<Label Grid.Column="0" Grid.Row="0" Text="{Binding Title}" LineBreakMode="TailTruncation" FontSize="25" Margin="20,0,0,0"/>
<Label Grid.Column="0" Grid.Row="1" Text="{Binding Link}" LineBreakMode="TailTruncation" FontSize="17" Margin="20,0,0,0"/>
<Image Margin="0,0,20,0"
IsVisible="{Binding IsVisible}"
Grid.Column="1"
Grid.Row="0"
Grid.RowSpan="2"
x:Name="DeleteButton"
Source="delete.png"
VerticalOptions="Center"
HeightRequest="20"
HorizontalOptions="Center"/>
</Grid>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
<StackLayout x:Name="FirstTimeOpenStack" HorizontalOptions="Center">
<Label Text="Please tap on the plus icon in the top right corner to add a website" />
</StackLayout>
</StackLayout>
Your App Delegate looks ok. You have an unhandled exception somewhere in the App() you are launching.
Here is one way to capture unhandled exceptions:
namespace WiFiVisualPager.iOS
{
// The UIApplicationDelegate for the application. This class is responsible for launching the
// User Interface of the application, as well as listening (and optionally responding) to
// application events from iOS.
[Register("AppDelegate")]
public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate
{
//
// This method is invoked when the application has loaded and is ready to run. In this
// method you should instantiate the window, load the UI into it and then make the window
// visible.
//
// You have 17 seconds to return from this method, or iOS will terminate your application.
//
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException;
global::Xamarin.Forms.Forms.Init();
DisplayCrashReport();
LoadApplication(new App());
return base.FinishedLaunching(app, options);
}
#region [Error handling]
//Credit: Peter Norman.
//https://peterno.wordpress.com/2015/04/15/unhandled-exception-handling-in-ios-and-android-with-xamarin/
//Minor compile fixes by David McCurley.
private static void TaskSchedulerOnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs unobservedTaskExceptionEventArgs)
{
var newExc = new Exception("TaskSchedulerOnUnobservedTaskException", unobservedTaskExceptionEventArgs.Exception);
LogUnhandledException(newExc);
}
private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs unhandledExceptionEventArgs)
{
var newExc = new Exception("CurrentDomainOnUnhandledException", unhandledExceptionEventArgs.ExceptionObject as Exception);
LogUnhandledException(newExc);
}
internal static void LogUnhandledException(Exception exception)
{
try
{
const string errorFileName = "Fatal.log";
var libraryPath = System.Environment.GetFolderPath(Environment.SpecialFolder.Resources); // iOS: Environment.SpecialFolder.Resources
var errorFilePath = Path.Combine(libraryPath, errorFileName);
var errorMessage = String.Format("Time: {0}\r\nError: Unhandled Exception\r\n{1}",
DateTime.Now, exception.ToString());
File.WriteAllText(errorFilePath, errorMessage);
// Log to Android Device Logging.
//Android.Util.Log.Error("Crash Report", errorMessage);
}
catch
{
// just suppress any error logging exceptions
}
}
/// <summary>
// If there is an unhandled exception, the exception information is diplayed
// on screen the next time the app is started (only in debug configuration)
/// </summary>
[Conditional("DEBUG")]
private static void DisplayCrashReport()
{
const string errorFilename = "Fatal.log";
var libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Resources);
var errorFilePath = Path.Combine(libraryPath, errorFilename);
if (!File.Exists(errorFilePath))
{
return;
}
var errorText = File.ReadAllText(errorFilePath);
var alertView = new UIAlertView("Crash Report", errorText, null, "Close", "Clear") { UserInteractionEnabled = true };
alertView.Clicked += (sender, args) =>
{
if (args.ButtonIndex != 0)
{
File.Delete(errorFilePath);
}
};
alertView.Show();
}
#endregion
}
}

How can I stop the clicking on a ViewCell from changing the background color for a brief time?

I have this XAML code:
<TableView x:Name="tableView" Intent="Settings" HasUnevenRows="True">
<TableSection>
<TableSection.Title>
Card Selection
</TableSection.Title>
<ViewCell Height="50">
<Grid>
<Grid x:Name="deselectGridLink" VerticalOptions="CenterAndExpand" Padding="20, 0">
<Label TextColor="Blue" Style="{DynamicResource ListItemTextStyle}" x:Name="deselectLink" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All" />
</Grid>
<Grid x:Name="deselectGridLabel" VerticalOptions="CenterAndExpand" Padding="20, 0">
<Label TextColor="Silver" Style="{DynamicResource ListItemTextStyle}" x:Name="deselectLabel" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All" />
</Grid>
</Grid>
</ViewCell>
<ViewCell Height="50">
<Grid x:Name="selectGridLink" VerticalOptions="CenterAndExpand" Padding="20, 0">
<Label TextColor="Blue" Style="{DynamicResource ListItemTextStyle}" x:Name="selectLink" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Select All" />
</Grid>
</ViewCell>
</TableSection>
</TableView>
When other parts of my code call: SetPageDetails() then the label in the grid is changed to a link or the link is changed to a label. So for this when it is a label I would like to have no background flash event and no action called.
I attach a tap gesture recognizer like this. Note it's all on one line but covers two lines so it's more visible here in the SO question:
deselectGridLink.GestureRecognizers
.Add(NewTapGestureForUpdateCategories(false));
private TapGestureRecognizer NewTapGestureForUpdateCategories(bool val)
{
return new TapGestureRecognizer()
{
Command = new Command(() =>
{
App.DB.UpdateAllCategoryGroups(val);
App.DB.UpdateAllCategories(val);
GetPageData();
RemoveTableViewClickSection();
tableView.Root.Add(CreateTableSection());
})
};
}
When the user clicks the row when deselectGridLink grid is visible then:
The deselectGridLink visibility is set to false
The deselectGridLabel visibility is set to true
private void SetPageDetails()
{
Title = App.cardCountForSelectedCategories + (App.cardCountForSelectedCategories == 1 ? " Card Selected" : " Cards Selected");
if (App.cardCountForSelectedCategories == 0)
{
deselectGridLink.IsVisible = false;
deselectGridLabel.IsVisible = true;
}
else
{
deselectGridLink.IsVisible = true;
deselectGridLabel.IsVisible = false;
}
}
The effect of this is that the grid link text will change to silver and the link becomes a label.
However even though it's a gray color label when the label is clicked there is still a brief background row color change from white to a dark color when the label is clicked. I assume it's just the way a view cell works.
Is there a way to suppress this from happening?
EDIT 1 - Updated answer as per updates to question. i.e. add support for switching between highlight enabled/disabled mode.
EDIT 2 - Restructure answer and add more details.
Option-1: Enable/disable view-cell through IsEnabled
The simplest option would be to use the IsEnabled property, which in turn enables/disables the background flash behavior. The only downside to this approach is that it will also disable the taps on child controls, i.e. tap events/gesture recognizer(s) will not be triggered if parent view-cell's IsEnabled is false.
For example:
XAML
<!-- Add name attribute to view-cell -->
<ViewCell x:Name="deselectCell" ..>
<Grid>
<Grid x:Name="deselectGridLink" ..
....
</ViewCell>
Code-behind
private void SetPageDetails()
{
if (App.cardCountForSelectedCategories == 0)
{
deselectCell.IsEnabled = false; //disable background flash
...
}
else
{
deselectCell.IsEnabled = true;
...
}
}
Recommendation 1 - Use data-binding and triggers
Instead of controlling visibility for each label in code-behind, you can use triggers and data-binding as follows (view-model will have a IsDeselectEnabled property):
<ViewCell IsEnabled="{Binding IsDeselectEnabled}" Height="50">
<Label Margin="20,0,20,0" Style="{DynamicResource ListItemTextStyle}" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All">
<Label.Triggers>
<DataTrigger TargetType="Label" Binding="{Binding IsDeselectEnabled}" Value="true">
<Setter Property="TextColor" Value="Blue" />
</DataTrigger>
<DataTrigger TargetType="Label" Binding="{Binding IsDeselectEnabled}" Value="false">
<Setter Property="TextColor" Value="Silver" />
</DataTrigger>
</Label.Triggers>
</Label>
</ViewCell>
Recommendation 2 - Use triggers with view as source
<ViewCell x:Name="deselectCell" Height="50">
<Label Margin="20,0,20,0" Style="{DynamicResource ListItemTextStyle}" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All">
<Label.Triggers>
<DataTrigger TargetType="Label" Binding="{Binding IsEnabled, Source={x:Reference deselectCell}}" Value="true">
<Setter Property="TextColor" Value="Blue" />
</DataTrigger>
<DataTrigger TargetType="Label" Binding="{Binding IsEnabled, Source={x:Reference deselectCell}}" Value="false">
<Setter Property="TextColor" Value="Silver" />
</DataTrigger>
</Label.Triggers>
</Label>
</ViewCell>
Option-2: Enable/disable highlight, but allow taps
To allow taps while toggling ViewCell's background-highlight behavior, we will need to implement platform-renderer(s).
In case of iOS, we can use SelectionStyle to toggle this behavior, while in case of android, we can use Clickable property.
Shared control:
public class CustomViewCell : ViewCell
{
public static readonly BindableProperty AllowHighlightProperty =
BindableProperty.Create(
"AllowHighlight", typeof(bool), typeof(CustomViewCell),
defaultValue: true);
public bool AllowHighlight
{
get { return (bool)GetValue(AllowHighlightProperty); }
set { SetValue(AllowHighlightProperty, value); }
}
}
iOS renderer:
[assembly: ExportRenderer(typeof(CustomViewCell), typeof(CustomViewCellRenderer))]
namespace SampleApp.iOS
{
public class CustomViewCellRenderer : ViewCellRenderer
{
UITableViewCell _nativeCell;
//get access to the associated forms-element and subscribe to property-changed
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
_nativeCell = base.GetCell(item, reusableCell, tv);
var formsCell = item as CustomViewCell;
if (formsCell != null)
{
formsCell.PropertyChanged -= OnPropertyChanged;
formsCell.PropertyChanged += OnPropertyChanged;
}
//and, update the style
SetStyle(formsCell);
return _nativeCell;
}
void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var formsCell = sender as CustomViewCell;
if (formsCell == null)
return;
//TODO: Trying to find a nicer and more robust way to dispose and unsubscribe :(
if (_nativeCell == null)
formsCell.PropertyChanged -= OnPropertyChanged;
if (e.PropertyName == CustomViewCell.AllowHighlightProperty.PropertyName)
{
SetStyle(formsCell);
}
}
private void SetStyle(CustomViewCell formsCell)
{
//added this code as sometimes on tap, the separator disappears, if style is updated before tap animation finishes
//https://stackoverflow.com/questions/25613117/how-do-you-prevent-uitableviewcellselectionstylenone-from-removing-cell-separato
Device.StartTimer(TimeSpan.FromMilliseconds(50), () => {
Device.BeginInvokeOnMainThread(() =>
{
if (formsCell.AllowHighlight)
_nativeCell.SelectionStyle = UITableViewCellSelectionStyle.Default;
else
_nativeCell.SelectionStyle = UITableViewCellSelectionStyle.None;
});
return false;
});
}
}
}
Android renderer:
[assembly: ExportRenderer(typeof(CustomViewCell), typeof(CustomViewCellRenderer))]
namespace SampleApp.Droid
{
public class CustomViewCellRenderer : ViewCellRenderer
{
Android.Views.View _nativeCell;
protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView, Android.Views.ViewGroup parent, Android.Content.Context context)
{
_nativeCell = base.GetCellCore(item, convertView, parent, context);
SetStyle();
return _nativeCell;
}
// this one is simpler as the base class has a nice override-able method for our purpose - so we don't need to subscribe
protected override void OnCellPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
base.OnCellPropertyChanged(sender, e);
if(e.PropertyName == CustomViewCell.AllowHighlightProperty.PropertyName)
{
SetStyle();
}
}
private void SetStyle()
{
var formsCell = Cell as CustomViewCell;
if (formsCell == null)
return;
_nativeCell.Clickable = !formsCell.AllowHighlight;
}
}
}
Sample usage 1 - Through data-binding
<local:CustomViewCell AllowHighlight="{Binding IsHighlightEnabled}" ..>
<Grid>
<Grid x:Name="deselectGridLink" ..
...
</local:CustomViewCell>
Sample usage 2 - Through code-behind
XAML
<!-- Add name attribute to view-cell -->
<local:CustomViewCell x:Name="deselectCell" ..>
<Grid>
<Grid x:Name="deselectGridLink" ..
...
</local:CustomViewCell>
Code-behind
private void SetPageDetails()
{
if (App.cardCountForSelectedCategories == 0)
{
deselectCell.AllowHighlight= false; //disable background flash
...
}
else
{
deselectCell.AllowHighlight= true;
...
}
}
Option-3: Disable highlight, selection for all items
This particularly applies to ListView. The updated question now specifies that the cells are part of TableView, so this option is no longer valid in current question context.
You will need to implement platform renderers to disable highlight colors, and add ItemTapped handler to ListView to disable selection by setting SelectedItem as null always. References used:
Disable highlight item
Disable selection
Code
To get started, create a custom view-cell:
public class NoSelectViewCell : ViewCell { }
Implement iOS renderer as:
[assembly: ExportRenderer(typeof(NoSelectViewCell), typeof(NoSelectViewCellRenderer))]
namespace SampleApp.iOS
{
public class NoSelectViewCellRenderer : ViewCellRenderer
{
public override UITableViewCell GetCell(Cell item, UITableViewCell reusableCell, UITableView tv)
{
var nativeCell = base.GetCell(item, reusableCell, tv);
nativeCell.SelectionStyle = UITableViewCellSelectionStyle.None;
return nativeCell;
}
}
}
Implement android renderer as:
[assembly: ExportRenderer(typeof(NoSelectViewCell), typeof(NoSelectViewCellRenderer))]
namespace SampleApp.Droid
{
public class NoSelectViewCellRenderer : ViewCellRenderer
{
protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView, Android.Views.ViewGroup parent, Android.Content.Context context)
{
var cell = base.GetCellCore(item, convertView, parent, context);
cell.Focusable = false;
cell.FocusableInTouchMode = false;
var listView = parent as Android.Widget.ListView;
if (listView != null)
{
listView.SetSelector(Android.Resource.Color.Transparent);
listView.CacheColorHint = Xamarin.Forms.Color.Transparent.ToAndroid();
}
return cell;
}
}
}
Sample Usage:
XAML
<ListView ItemTapped="Handle_ItemTapped">
<ListView.ItemTemplate>
<DataTemplate>
<local:NoSelectViewCell Height="50">
<Grid>
<Grid x:Name="deselectGridLink" VerticalOptions="CenterAndExpand" Padding="20, 0">
<Label TextColor="Blue" Style="{DynamicResource ListItemTextStyle}" x:Name="deselectLink" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All" />
</Grid>
<Grid x:Name="deselectGridLabel" VerticalOptions="CenterAndExpand" Padding="20, 0">
<Label TextColor="Silver" Style="{DynamicResource ListItemTextStyle}" x:Name="deselectLabel" HorizontalOptions="StartAndExpand" VerticalOptions="Center" Text="Deselect All" />
</Grid>
</Grid>
</local:NoSelectViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Code-behind
void Handle_ItemTapped(object sender, Xamarin.Forms.ItemTappedEventArgs e)
{
// don't do anything if we just de-selected the row
if (e.Item == null) return;
// do something with e.SelectedItem
((ListView)sender).SelectedItem = null; // de-select the row
}
What G.Sharada proposes is very nicely working for iOS, but on Android I still had blinks on click.
Adding this line to the styles file solved the problem.
<item name="android:colorActivatedHighlight">#android:color/transparent</item>

Windows Phone 8.1 LongListSelector with Images

I have a long list selector in my application. The list should display an icon and a caption below it. It appears as a grid as seen in the image below
The icon should be downloaded from the server and displayed, the icon is downloaded from the server as an byte array which can be used to generate the Bitmap. How do i bind the Bitmap to the Image in my LongListSelectors template. My data template is as below.
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<Grid Margin="5,5,5,5" Background="{StaticResource PhoneAccentBrush}">
<Grid VerticalAlignment="Center" HorizontalAlignment="Center">
<Image Height="70" Width="70" Margin="10,0,0,0" Source="/Assets/Images/appimg.png">
</Image>
</Grid>
<StackPanel VerticalAlignment="Bottom" HorizontalAlignment="Center">
<TextBlock Margin="5,5,5,5" TextTrimming="WordEllipsis" TextWrapping="NoWrap" Text="{Binding appTitle}"></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
Currently in the template the icon path is hardcoded. Please provide some input on how to bind the bitmap to the image.
Use a Conveter
Assuming the assembly name of the phone project is: PhoneApp1
Create a converter with the following code:
using System;
namespace PhoneApp1.Converters
{
using System.Globalization;
using System.IO;
using System.Windows.Data;
using System.Windows.Media.Imaging;
public class ByteToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var data = value as byte[];
if (data != null)
{
using(var stream = new MemoryStream(data))
{
var bitmapImage = new BitmapImage();
bitmapImage.SetSource(stream);
return bitmapImage;
}
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
use the following namespace declaration in your xaml:
xmlns:converters="clr-namespace:PhoneApp1.Converters"
XAML:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid.Resources>
<converters:ByteToImageConverter x:Key="ByteToImageConverter" />
</Grid.Resources>
<phone:LongListSelector x:Name="lstTiles">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<Grid Margin="5,5,5,5" Background="{StaticResource PhoneAccentBrush}">
<Grid VerticalAlignment="Center" HorizontalAlignment="Center">
<Image Height="70" Width="70" Margin="10,0,0,0" Source="{Binding Image, Converter={StaticResource ByteToImageConverter}}">
</Image>
</Grid>
<StackPanel VerticalAlignment="Bottom" HorizontalAlignment="Center">
<TextBlock Margin="5,5,5,5" TextTrimming="WordEllipsis" TextWrapping="NoWrap" Text="{Binding Title}"></TextBlock>
</StackPanel>
</Grid>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
Code Behind and Model
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
this.BindTiles();
}
private void BindTiles()
{
var sampleRandomImageBytes = Convert.FromBase64String(#"iVBORw0KGgoAAAANSUhEUgAAAGgAAABbCAYAAACf8sCiAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADeSURBVHhe7dExAQAwDMCgmVj9O21tcORAAe/PbFwF4QrCFYQrCFcQriBcQbiCcAXhCsIVhCsIVxCuIFxBuIJwBeEKwhWEKwhXEK4gXEG4gnAF4QrCFYQrCFcQriBcQbiCcAXhCsIVhCsIVxCuIFxBuIJwBeEKwhWEKwhXEK4gXEG4gnAF4QrCFYQrCFcQriBcQbiCcAXhCsIVhCsIVxCuIFxBuIJwBeEKwhWEKwhXEK4gXEG4gnAF4QrCFYQrCFcQriBcQbiCcAXhCsIVhCsIVxCuIFxBuIJwBeEKos0e0/44s5MWYKUAAAAASUVORK5CYII=");
var data = new[]
{
new AppTitle { Title = "Tile 1", Image = sampleRandomImageBytes }, new AppTitle { Title = "Tile 2", Image = sampleRandomImageBytes },
new AppTitle { Title = "Tile 3", Image = sampleRandomImageBytes }, new AppTitle { Title = "Tile 4", Image = sampleRandomImageBytes }
};
lstTiles.ItemsSource = data;
}
}
public class AppTitle
{
public string Title { get; set; }
public byte[] Image { get; set; }
}

Changing ListBox feeds into Pivot (SyndicationItem message instead of text)

So, I found a way of creating RSS feed app for Windows Phone. It binds SyndicationFeed items into Listbox template. And it works great!
However, I wanted to make an app, that handles a bit different. I thought, that changing Listbox template into Pivot would be quite easy. The problem is, that instead of articles I get only "System.ServiceModel.Syndication.SyndicationItem".
I am out of ideas how to fix that, could anyone help me?
Xaml:
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Popup Name="myPopup" IsOpen="True" Width="Auto">
<ProgressBar Height="Auto" IsIndeterminate="True" Width="480" />
</Popup>
<controls:Pivot Name="FeedPivot" Loaded="loadFeed">
<controls:Pivot.Title>
<TextBlock FontSize="56"> Komorkomania </TextBlock>
</controls:Pivot.Title>
<controls:Pivot.HeaderTemplate>
<DataTemplate>
<Grid>
<TextBlock>test1</TextBlock>
</Grid>
</DataTemplate>
</controls:Pivot.HeaderTemplate>
<controls:Pivot.DataContext>
<DataTemplate>
<StackPanel>
<TextBlock>test</TextBlock>
<!--
<TextBlock FontWeight="Bold" FontSize="28" Name="feedTitle" TextWrapping="Wrap" Margin="12,0,0,0" HorizontalAlignment="Left" Foreground="{StaticResource PhoneAccentBrush}" Text="{Binding Title.Text, Converter={StaticResource RssTextTrimmer}}" Width="430" />
<TextBlock Name="feedSummary" FontSize="24" TextWrapping="Wrap" Margin="12,0,0,0" Text="{Binding Summary.Text, Converter={StaticResource RssTextTrimmer}}" Width="430" />
<TextBlock Name="feedPubDate" Foreground="{StaticResource PhoneSubtleBrush}" Margin="12,0,0,10" Text="{Binding PublishDate.DateTime}" Width="430" />
-->
</StackPanel>
</DataTemplate>
</controls:Pivot.DataContext>
</controls:Pivot>
</Grid>
Code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Microsoft.Phone.Controls;
using System.IO;
using System.ServiceModel.Syndication;
using System.Xml;
using Microsoft.Phone.Tasks;
using Microsoft.Phone.Shell;
using System.ComponentModel;
using System.Windows.Controls.Primitives;
namespace KomorkomaniaRss
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
myPopup.IsOpen = true;
}
// Co robimy jak już w końcu się ściągnie?
private void webClient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error != null)
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
MessageBox.Show(e.Error.Message);
});
}
else
{
// Save the feed into the State property in case the application is tombstoned.
this.State["feed"] = e.Result;
UpdateFeedList(e.Result);
myPopup.IsOpen = false;
}
}
// This method determines whether the user has navigated to the application after the application was tombstoned.
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
// First, check whether the feed is already saved in the page state.
if (this.State.ContainsKey("feed"))
{
// Get the feed again only if the application was tombstoned, which means the ListBox will be empty.
// This is because the OnNavigatedTo method is also called when navigating between pages in your application.
// You would want to rebind only if your application was tombstoned and page state has been lost.
if (FeedPivot.Items.Count == 0)
{
UpdateFeedList(State["feed"] as string);
}
}
}
// This method sets up the feed and binds it to our ListBox.
private void UpdateFeedList(string feedXML)
{
// Load the feed into a SyndicationFeed instance.
StringReader stringReader = new StringReader(feedXML);
XmlReader xmlReader = XmlReader.Create(stringReader);
SyndicationFeed feed = SyndicationFeed.Load(xmlReader);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
// Bind the list of SyndicationItems to our ListBox.
FeedPivot.ItemsSource = feed.Items;
});
}
public void feedLoader()
{
myPopup.IsOpen = true;
WebClient webClient = new WebClient();
webClient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(webClient_DownloadStringCompleted);
webClient.DownloadStringAsync(new System.Uri("http://komorkomania.pl/feed/rss2"));
}
private void runBrowser(object sender)
{
ListBox listBox = sender as ListBox;
if (listBox != null && listBox.SelectedItem != null)
{
// Get the SyndicationItem that was tapped.
SyndicationItem sItem = (SyndicationItem)listBox.SelectedItem;
// Set up the page navigation only if a link actually exists in the feed item.
if (sItem.Links.Count > 0)
{
// Get the associated URI of the feed item.
Uri uri = sItem.Links.FirstOrDefault().Uri;
// Create a new WebBrowserTask Launcher to navigate to the feed item.
// An alternative solution would be to use a WebBrowser control, but WebBrowserTask is simpler to use.
WebBrowserTask webBrowserTask = new WebBrowserTask();
webBrowserTask.Uri = uri;
webBrowserTask.Show();
}
}
}
// The SelectionChanged handler for the feed items
private void feedListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
runBrowser(sender);
}
private void loadFeed(object sender, RoutedEventArgs e)
{
feedLoader();
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
}
}
}
There are some "leftovers" for handling another actions. Please ignore them, I didn't get to it yet. The biggest focus is to make it show text after app is loaded.
I bealive that I fixed that. There was a problem with Pivot concept. This is how I fixed that:
<controls:Pivot Name="FeedPivot" Loaded="loadFeed" ScrollViewer.VerticalScrollBarVisibility="Visible" Tap="feedPivotTap" Margin="0,88,0,0" LoadedPivotItem="getPivotItem">
<controls:Pivot.HeaderTemplate>
<DataTemplate>
<TextBlock FontWeight="Bold" FontSize="28" Name="feedTitle" TextWrapping="Wrap" Margin="12,0,0,0" HorizontalAlignment="Left" Foreground="#FF5DBA00" Text="{Binding Title.Text, Converter={StaticResource RssTextTrimmer}}" Width="430" />
</DataTemplate>
</controls:Pivot.HeaderTemplate>
<controls:Pivot.ItemTemplate>
<DataTemplate>
<ScrollViewer>
<StackPanel>
<TextBlock Name="feedSummary" FontSize="24" TextWrapping="Wrap" Margin="12,0,0,0" Text="{Binding Summary.Text, Converter={StaticResource RssTextTrimmer}}" Width="430" />
<TextBlock Name="feedPubDate" Foreground="{StaticResource PhoneSubtleBrush}" Margin="12,0,0,10" Text="{Binding PublishDate.DateTime}" Width="430" />
</StackPanel>
</ScrollViewer>
</DataTemplate>
</controls:Pivot.ItemTemplate>
</controls:Pivot>

Resources