I have a Listview which has toggle switches. I want to select only one item in Listview with toggle switch. When I select second toggle then first toggle must be de-active again. For example in the picture; When I select Rekorida and then I select Merchandizing , Rekorida must turn back disable. Every time only one option can be active. Is it possible to do it in Xamarin?
My listView Code :
<ScrollView>
<ListView x:Name="ShipListView" HasUnevenRows="True" SeparatorVisibility="Default" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Margin="4, 3, 4, 3" Padding="2" BackgroundColor="White">
<Grid.RowDefinitions HeightRequest="40">
<RowDefinition></RowDefinition>
<!--<RowDefinition Height="20"></RowDefinition>-->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="40*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Text="{Binding ShipName}" TextColor="DeepPink" Grid.Column="0" FontAttributes="Bold"/>
<Switch IsToggled="{Binding Selected}" Grid.Column="2" Toggled="OnShipToggled" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollView>
And My function
async void OnShipToggled(object sender, ToggledEventArgs e)
{
checkShipSelected = !checkShipSelected;
if(checkShipSelected)
{
ViewCell cell = (sender as Switch).Parent.Parent as ViewCell;
ParametersMemberShipInformation model = cell.BindingContext as ParametersMemberShipInformation;
if (model != null)
{
selectedMemberShipOid = model.Oid;
}
}
else
{
return;
}
}
I'm trying get id of selected item in listview and I do this successfully. Bu I want users can only select one item at the same time because of nice visuality and not be confused.
Fetch your view model in your code behind file and then filter out the selected toggle and marked rest of them as false
private YourViewModel MyViewModel { get => BindingContext as YourViewModel; }
if (model != null)
{
selectedMemberShipOid = model.Oid;
MyViewModel.ShipListView.Where(x=> x.Oid !=
selectedMemberShipOid).Foreach(x=> x.Selected = false)
}
Related
This is how the xaml looks like:
<ListView HorizontalContentAlignment="Stretch"
x:Name="listViewMessages"
Grid.Column="1"
ItemsSource="{x:Bind MessageViewModel.Messages}"
Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ActualHeight}"
ItemClick="listViewMessages_ItemClick">
<ListView.HeaderTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
...
</Grid.ColumnDefinitions>
</Grid>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Message">
<Grid HorizontalAlignment="{x:Bind Path=MineToHorizontalAlignment()}" Background="{x:Bind Path=MineBackgroundColor()}" CornerRadius="8" Margin="0,6,0,2" Padding="6">
<Grid.ColumnDefinitions>
...
</Grid.ColumnDefinitions>
...
<Button Grid.Column="5" Click="Button_Click">D</Button>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
So when I will click the button I want to get the list view item of the clicked button.
How can I achieve this?
Edit:
I changed the xaml example to a more specific one.
You need to use the VisualTreeHelper Class to traverse the visual tree. Here is a C++/WinRT utility to walk the parents recursively:
template <typename T>
T GetParent(DependencyObject obj)
{
if (!obj)
return nullptr;
auto parent = Microsoft::UI::Xaml::Media::VisualTreeHelper::GetParent(obj);
if (!parent)
return nullptr;
auto parentAs = parent.try_as<T>();
if (parentAs)
return parentAs;
return GetParent<T>(parent);
}
And it's C# counterpart for what it's worth:
public static T GetParent<T>(DependencyObject obj) => (T)GetParent(obj, typeof(T));
public static object GetParent(DependencyObject obj, Type type)
{
if (obj == null)
return null;
var parent = VisualTreeHelper.GetParent(obj);
if (parent == null)
return null;
if (type.IsAssignableFrom(parent.GetType()))
return parent;
return GetParent(parent, type);
}
So you would call it like this:
void MainWindow::Button_Click(IInspectable const& sender, RoutedEventArgs const&)
{
auto listView = GetParent<Controls::ListView>(sender.try_as<DependencyObject>());
}
I have a registration form, when the user is entering data such as Email, below that Entry control there is a Label that appears if there is an error in the input. So my problem is that the virtual keyboard hides the Label showing input errors and I don't want that to happen.
With keyboard.jpg without keyboard.jpg
It will be that there will be some way to move the content of the form a little higher so that the Control Entry can be seen along with the Error Label
<StackLayout>
<Entry
Keyboard="Email"
MaxLength="30"
Placeholder="Enter Email"
ReturnType="Next"
Style="{StaticResource BorderlessEntryStyle}"
Text="{Binding Email.Value}">
<Entry.Behaviors>
<behaviorsValidate:EventToCommandBehavior Command="{Binding ValidateEmailCommand}" EventName="TextChanged" />
</Entry.Behaviors>
</Entry>
<Label
Margin="4,-4,0,0"
FontSize="12"
IsVisible="{Binding Email.IsValid, Converter={StaticResource InverseBoolConverter}}"
Style="{StaticResource SimpleLabelStyle}"
Text="{Binding Email.Errors, Converter={StaticResource FirstValidationErrorConverter}}"
TextColor="{DynamicResource Red}"
VerticalOptions="FillAndExpand" />
</StackLayout>
About adjusting elements when keyboard shows in Xamarin Forms, find one way to do this.
On android you just need to add your elements inside a Grid and use the platform specific UseWindowSoftInputModeAdjust Resize in the Application XAML.
firstly, create a new class that extend from Grid in Shared code.
public class KeyboardView: Grid
{
}
Then adding your control inside it.
<views:KeyboardView Padding="0,60,0,0"
VerticalOptions="FillAndExpand">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="60" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Image Source="ic_test"
HeightRequest="80"
WidthRequest="80"
HorizontalOptions="CenterAndExpand"
Grid.Row="0"/>
<Label Text="Login"
FontAttributes="Bold"
TextColor="CornflowerBlue"
HorizontalOptions="CenterAndExpand"
FontSize="25"
VerticalOptions="Center"
Margin="0,20,0,0"
Grid.Row="1"
x:Name="welcomeText"/>
<Entry Placeholder="Email"
Grid.Row="2"
Margin="20,0"
x:Name="email"
ReturnType="Done"
Keyboard="Email"/>
<Entry Placeholder="Password"
Margin="20,0"
Grid.Row="3"
HeightRequest="50"
x:Name="password"
ReturnType="Done"
IsPassword="true"/>
<Button VerticalOptions="EndAndExpand"
BackgroundColor="CornflowerBlue"
HeightRequest="60"
TextColor="White"
CornerRadius="0"
Grid.Row="4"
Text="Login"/>
</views:KeyboardView>
Thirdly, add platform specific UseWindowSoftInputModeAdjust with Resize value on the Application XAML
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="KeyboardSample.App"
xmlns:android="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core"
android:Application.WindowSoftInputModeAdjust="Resize">
On iOS we have to create a custom renderer to do the resize. Don't test on ios device.
[assembly: ExportRenderer(typeof(KeyboardView), typeof(KeyboardViewRenderer))]
namespace KeyboardSample.iOS.Renderers
{
public class KeyboardViewRenderer : ViewRenderer
{
NSObject _keyboardShowObserver;
NSObject _keyboardHideObserver;
protected override void OnElementChanged(ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
RegisterForKeyboardNotifications();
}
if (e.OldElement != null)
{
UnregisterForKeyboardNotifications();
}
}
void RegisterForKeyboardNotifications()
{
if (_keyboardShowObserver == null)
_keyboardShowObserver = UIKeyboard.Notifications.ObserveWillShow(OnKeyboardShow);
if (_keyboardHideObserver == null)
_keyboardHideObserver = UIKeyboard.Notifications.ObserveWillHide(OnKeyboardHide);
}
void OnKeyboardShow(object sender, UIKeyboardEventArgs args)
{
NSValue result = (NSValue)args.Notification.UserInfo.ObjectForKey(new NSString(UIKeyboard.FrameEndUserInfoKey));
CGSize keyboardSize = result.RectangleFValue.Size;
if (Element != null)
{
Element.Margin = new Thickness(0, 0, 0, keyboardSize.Height); //push the entry up to keyboard height when keyboard is activated
}
}
void OnKeyboardHide(object sender, UIKeyboardEventArgs args)
{
if (Element != null)
{
Element.Margin = new Thickness(0); //set the margins to zero when keyboard is dismissed
}
}
void UnregisterForKeyboardNotifications()
{
if (_keyboardShowObserver != null)
{
_keyboardShowObserver.Dispose();
_keyboardShowObserver = null;
}
if (_keyboardHideObserver != null)
{
_keyboardHideObserver.Dispose();
_keyboardHideObserver = null;
}
}
}
}
I want to reload items into my flowlistview when it reached its bottom. therfore, I would need a function that fires once the end is reached.
I have this flowlistview set up:
<flv:FlowListView
Grid.Column="1"
FlowItemAppearing="listview_allAds_FlowItemAppearing"
FlowColumnCount="2"
IsPullToRefreshEnabled="True"
Refreshing="listview_allAds_Refreshing"
Margin="0,10,0,0"
VerticalScrollBarVisibility="Never"
FlowItemTapped="listview_allAds_FlowItemTapped"
HasUnevenRows="True"
x:Name="listview_allAds" >
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<ContentView Padding="4,3,3,4"> <!--only way to set padding-->
<Frame BorderColor="#ffffff"
HasShadow="True"
Padding="0"
CornerRadius="{OnPlatform Android=5, iOS=5}">
<Grid ...>
</Frame>
</ContentView>
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
I red that there is a lot you can do with "commands" but I am not sure how to do this.
Can you help me out here?
Thanks!
You could use the FlowItemAppearing. The event you set in your code works well.
async void listview_allAds_ItemAppearing(object sender, ItemVisibilityEventArgs e)
{
if (e.ItemIndex == infos.Count/2)//2 is the FlowColumnCount of your FlowListView
{
await DisplayAlert("Alert", "Scrolled to the bottom", "OK");
}
}
In our UWP app the DataTemplate for MyListView is set in the code behind to either DataTemplateA or DataTemplateB in Page.Resources. Each data template contains a grid (TopGrid) which contains a DisplayGridButton and another grid (DisplayGrid).
DisplayGrid contains SecondListView and a HideGridButton
DisplayGridButton should show DisplayGrid. HideGridButton should collapse DisplayGrid.
The XAML is
<Page.Resources>
<DataTemplate x:Key="DataTemplateA">
<Grid Name="TopGrid">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<TextBox/>
<Button Name="DisplayGridButton" Content="Show" Margin="10,0" Click="DisplayGridButton_Click"/>
</StackPanel>
<Grid Name="DisplayGrid" Grid.Row="1" Visibility="Collapsed">
<StackPanel>
<Button Name="HideGridButton" Content="Hide" Click="HideGridButton_Click"/>
<ListView Name="SecondListView">
<ListView.ItemTemplate>
<DataTemplate >
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
</Grid>
</DataTemplate>
<DataTemplate x:Key="DataTemplateB">
<Grid Name="TopGrid">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<TextBox/>
<Button Name="DisplayGridButton" Content="Show" Margin="10,0" Click="DisplayGridButton_Click"/>
</StackPanel>
<Grid Name="DisplayGrid" Grid.Row="1" Visibility="Collapsed">
<StackPanel>
<Button Name="HideGridButton" Content="Hide" Click="HideGridButton_Click"/>
<ListView Name="SecondListView">
<ListView.ItemTemplate>
<DataTemplate >
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView Name="MyListView">
</ListView>
</Grid>
DataTemplateA or DataTemplateB is set in the code behind.
if (condition)
{
MyListView.ItemTemplate = (DataTemplate)Resources["DataTemplateA"];
}
else
{
MyListView.ItemTemplate = (DataTemplate)Resources["DataTemplateB"];
}
In the Code behind I can create the event handler but I cannot access the DisplayGrid to make it visible or to collapse it.
I would normally set visibility like this.
private void DisplayGridButton_Click(object sender, RoutedEventArgs e)
{
DisplayGrid.Visibility = Visibility.Visible;
}
private void HideGridButton_Click(object sender, RoutedEventArgs e)
{
DisplayGrid.Visibility = Visibility.Collapsed;
}
How do I access the DisplayGrid in the DataTemplate from the button click events?
Since the grid is defined in a template, you'll have to dig it out at runtime. (If you could reference it as "DisplayGrid" from code-behind, you wouldn't know which listview item it belonged to anyway.)
Implement click handlers something like this:
private void DisplayGridButton_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
StackPanel stackPanel = button?.Parent as StackPanel;
Grid grid = stackPanel?.Parent as Grid;
if (grid != null)
{
Grid displayGrid = FindVisualChild<Grid>(grid, "DisplayGrid");
if (displayGrid != null)
{
displayGrid.Visibility = Visibility.Visible;
}
}
}
private void HideGridButton_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
StackPanel stackPanel = button?.Parent as StackPanel;
Grid grid = stackPanel?.Parent as Grid;
if (grid != null)
{
grid.Visibility = Visibility.Collapsed;
}
}
(Fair warning: The way this code finds the appropriate parent to start from is a bit brittle; it might break if the template changes. Searching by name would be better, but I don't have anything handy right now.)
Here is the helper method that finds a named child in the visual tree:
public static T FindVisualChild<T>(
DependencyObject parent,
string name = null)
where T : DependencyObject
{
if (parent != null)
{
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
T candidate = child as T;
if (candidate != null)
{
if (name == null)
{
return candidate;
}
FrameworkElement element = candidate as FrameworkElement;
if (name == element?.Name)
{
return candidate;
}
}
T childOfChild = FindVisualChild<T>(child, name);
if (childOfChild != null)
{
return childOfChild;
}
}
}
return default(T);
}
(This method can also search by type only; just pass null as the name.)
I want to implement the swipe to delete functionality in Xamrin Forms, for which i have tried the following.
Wrote a custom renderer for the list view and in the "OnElementChanged" of the renderer am able to access the binded command to the "CustomListView" and am able to add this command to the Swipe Gesture as added below.
swipeGestureRecognizer = new UISwipeGestureRecognizer (() => {
if (command == null) {
Console.WriteLine ("No command set");
return;}
command.Execute (null);
});
However i am having trouble in accessing the specific row(swiped row), so that i could make a button visible/hidden on the swiped row in the list view. Please could you recommend a way to implement the same?
Swipe to delete is now built into Xamarin Froms ListViews using a ContextAction. Here is the most basic tutorial of how to do it. It is very easy to implement.
http://developer.xamarin.com/guides/cross-platform/xamarin-forms/working-with/listview/
You could do something like this:
protected override void OnElementChanged (ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged (e);
var swipeDelegate = new SwipeRecogniserDelegate ();
swipeGestureRecognizer = new UISwipeGestureRecognizer {
Direction = UISwipeGestureRecognizerDirection.Left,
Delegate = swipeDelegate
};
swipeGestureRecognizer.AddTarget (o => {
var startPoint = swipeDelegate.GetStartPoint ();
Console.WriteLine (startPoint);
var indexPath = this.Control.IndexPathForRowAtPoint(startPoint);
if(listView.SwipeCommand != null) {
listView.SwipeCommand.Execute(indexPath.Row);
}
});
this.Control.AddGestureRecognizer (swipeGestureRecognizer);
this.listView = (SwipableListView)this.Element;
}
The key is SwipeRecogniserDelegate. its implemented like so:
public class SwipeRecogniserDelegate : UIGestureRecognizerDelegate
{
PointF startPoint;
public override bool ShouldReceiveTouch (UIGestureRecognizer recognizer, UITouch touch)
{
return true;
}
public override bool ShouldBegin (UIGestureRecognizer recognizer)
{
var swipeGesture = ((UISwipeGestureRecognizer)recognizer);
this.startPoint = swipeGesture.LocationOfTouch (0, swipeGesture.View);
return true;
}
public PointF GetStartPoint ()
{
return startPoint;
}
}
I was able to accomplish this with the new Xamarin.Forms
SwipeView
Pass the current row into the CommandParameter, and use it in the event handler.
FYI: For some reason the SwipeView has a default BackgroundColor of white, which you can override with something else to match your theme.
Xaml:
<ListView Margin="-20,0,0,0" x:Name="photosListView" ItemSelected="OnItemSelected" VerticalOptions="FillAndExpand" SeparatorColor="Gray" VerticalScrollBarVisibility="Default" HasUnevenRows="true" SeparatorVisibility="Default" Background="{StaticResource PrimaryDark}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<SwipeView BackgroundColor="{StaticResource PrimaryDark}" >
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem Text="Delete" BackgroundColor="LightPink" Clicked="OnDeleteRow" CommandParameter="{Binding .}" />
</SwipeItems>
</SwipeView.RightItems>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackLayout Orientation="Horizontal">
<CheckBox IsVisible="{Binding SelectEnabled}" Color="{StaticResource White}" IsChecked="{Binding Selected}" Margin="20,0,-15,0" CheckedChanged="OnItemCheckedChanged" />
<Grid WidthRequest="70" HeightRequest="50">
<Grid.Margin>
<OnPlatform x:TypeArguments="Thickness" Android="15,0,0,0" iOS="10,0,0,0" />
</Grid.Margin>
<Image Aspect="AspectFill" Source="{Binding ThumbImageSource}" HorizontalOptions="Fill" />
</Grid>
</StackLayout>
<StackLayout Grid.Column="1" Spacing="0" Padding="0" Margin="0,5,0,0">
<Label Text="{Binding Photo.Description}" TextColor="{StaticResource TextColour}" FontSize="16" FontAttributes="Bold" />
<Label Text="{Binding DateTakenString}" TextColor="{StaticResource TextColour}" FontSize="14" />
</StackLayout>
</Grid>
</SwipeView>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
cs:
public async void OnDeleteRow(object sender, EventArgs e)
{
if (await GetDeleteRowConfirmationFromUser())
{
SwipeItem si = sender as SwipeItem;
PhotoListItem itemToDelete = si.CommandParameter as PhotoListItem;
LocalDatabaseService db = new LocalDatabaseService();
db.DeletePhoto(itemToDelete.Photo);
_listItems.Remove(itemToDelete);
}
}