.Net Maui converter in ColumnDefinition - converters

In my view I have a Grid with a number of columns. I would like to hide/show one column and resize the others based on an ObservableProperty in the view model.
I have tried to bind to the Width of the ColumnDefinition and use a converter to convert the boolean to a GridLength. But no matter how I try, my breakpoint in the converter is not hit.
Is it simply not possible? a bug? or have I just not found the correct syntax yet?
My latest attempt looks like this:
<ColumnDefinition Width="{Binding ShowColorColumn, Converter={StaticResource converters:BoolToGridLengthConverter}, ConverterParameter='80|0'}" />

You can bind the ColumnDefinitions with ColumnDefinitionCollection, and add several ColumnDefinition object into it.
like:
<Grid ColumnDefinitions="{Binding columns}">
<Label Grid.Row="0" Grid.Column="0" BackgroundColor="Red" Text="Label1"/>
<Label Grid.Row="0" Grid.Column="1" BackgroundColor="Yellow" Text="Label2"/>
<Label Grid.Row="0" Grid.Column="2" BackgroundColor="Green" Text="Label3"/>
</Grid>
code bdehind:
public ColumnDefinitionCollection columns { set; get; }
public string text { set; get; }
public GridTestPage()
{
columns = new ColumnDefinitionCollection();
ColumnDefinition column1=new ColumnDefinition();
ColumnDefinition column2 = new ColumnDefinition();
ColumnDefinition column3 = new ColumnDefinition();
column1.Width = 20;
column2.Width = 40;
column3.Width = 60;
columns.Add(column1);
columns.Add(column2);
columns.Add(column3);
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
BindingContext = this;
}

Related

Running a delayed (async) task in view-model causes view to not update

I have a profile view-model that in it's constructor calls the DB for the user that just logged. It works fine but if I delay it by 3s then I assume one of the threads continues and creates the view with nothing inside. I use a data template selector to construct the view and if I delay the task then the view will not render anything. Again, if I don't delay it works fine but I need to make sure that it will work if the server takes longer to respond and 3s is not that much in some cases
here is my view model
public class ProfileViewModel: BaseViewModel
{
private ObservableCollection<Stylist> users;
public ObservableCollection<Stylist> Users
{
get => users;
set
{
users = value;
OnPropertyChanged();
}
}
private Stylist user;
public Stylist User { get => user; set { user = value; OnPropertyChanged(); } }
private bool isStylist;
public AsyncCommand TestCommand { get; set; }
public ProfileViewModel()
{
IsBusy = true;
TestCommand = new AsyncCommand(Test);
Users = new ObservableCollection<Stylist>();
Task.Run(async () =>
{
int id = ((App)App.Current).ID;
if (!((App)App.Current).IsStylist)
{
var user = await DB.GetUser(id);
User = CopyUserToStylist(user);
}
else User = await DB.GetStylist(id);
isStylist = User.IsStylist;
await Task.Delay(3000); // without this line everything is ok
Users.Add(User);
OnPropertyChanged(nameof(Users));
IsBusy = false;
});
}
the view
<ContentPage.Resources>
<DataTemplate x:Key="UserTemplate">
<StackLayout>
<Label Text="user logged"/>
<Label Text="{Binding Name}"/>
</StackLayout>
</DataTemplate>
<DataTemplate x:Key="StylistTemplate">
<StackLayout>
<Label Text="stylist logged"/>
<Label Text="{Binding Name}"/>
<Label Text="{Binding Location}"/>
<Button Text="Test stylist"
Command="{Binding TestCommand}"/>
</StackLayout>
</DataTemplate>
<local:UserTypeSelector
x:Key="personDataTemplateSelector"
StylistTemplate="{StaticResource StylistTemplate}"
UserTemplate="{StaticResource UserTemplate}" />
</ContentPage.Resources>
<ContentPage.Content>
<StackLayout Margin="10"
BindableLayout.ItemTemplateSelector="{StaticResource personDataTemplateSelector}"
BindableLayout.ItemsSource="{Binding Users}">
<Label Text="FML"/>
<Label Text="Loading"
IsVisible="{Binding IsBusy}"/>
<Button Text="Test"
Command="{Binding TestCommand}"/>
</StackLayout>
</ContentPage.Content>
and the db calls
public static async Task<User> GetUser(int id)
{
await Init();
return await db.Table<User>().Where(x => x.id == id).FirstAsync();
}
public static async Task<Stylist> GetStylist(int id)
{
await Init();
return await db.Table<Stylist>().Where(x => x.id == id).FirstAsync();
}
if I don't delay it works fine but I need to make sure that it will work if the server takes longer to respond and 3s is not that much in some cases
If you use Await in Task, the code starting from Await will be in another thread, not in main thread, so the UI will be not updated.
You can use Device.BeginInvokeOnMainThread(Action) Method to update UI by changing data source.
I do one sample that you can take a look.
<StackLayout Margin="10" BindableLayout.ItemsSource="{Binding Users}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<StackLayout>
<Label Text="{Binding name}" />
<Label Text="{Binding age}" />
</StackLayout>
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
public partial class Page52 : ContentPage
{
public Page52()
{
InitializeComponent();
this.BindingContext = new ProfileViewModel();
}
}
public class ProfileViewModel:ViewModelBase
{
public ObservableCollection<user1> Users { get; set; }
public ProfileViewModel()
{
Users = new ObservableCollection<user1>();
Task.Run(async()=> {
user1 user = new user1() { name = "cherry", age = 18 };
await Task.Delay(3000);
Device.BeginInvokeOnMainThread(()=> {
Users.Add(user);
});
});
}
}
public class user1
{
public string name { get; set; }
public int age { get; set; }
}

I have a picker that is in a collectionview but I'm unsure how to get the value

I have a collectionview which has a [Label(day of the week] - [checkbox] - [picker with times].
I want the user to be able to select a day and time, after that I want to then pass those values to my database. So far I am able to select the day and pass this value on. However I am struggling to refence the value in the picker. I think this is due to it being a list in my object. I have tried booking.Listtimes but that is just a list. I want the value selected.
My ViewModel:
public class RepeatMonthly
{
public string Day { get; set; }
public bool Selected { get; set; }
public List<WalkTimes> _ListTimes { get; set; }
}
private WalkTimes _selectedTimes;
public WalkTimes SelectedTimes
{
get
{
return _selectedTimes;
}
set
{
SetProperty(ref _selectedTimes, value);
}
}
public ObservableCollection<RepeatMonthly> DayList { get; set; }
public CreateWeeklyScheduleViewModel()
{
ListTimes = WalkTimesService.GetTimes().OrderBy(c => c.Key).ToList();
DayList = new ObservableCollection<RepeatMonthly>()
{
new RepeatMonthly(){Day="Every Monday", Selected = false, _ListTimes = ListTimes},
new RepeatMonthly(){Day="Every Tuesday", Selected = false, _ListTimes = ListTimes},
new RepeatMonthly(){Day="Every WednesDay", Selected = false, _ListTimes = ListTimes},
new RepeatMonthly(){Day="Every Thursday", Selected = false, _ListTimes = ListTimes},
new RepeatMonthly(){Day="Every Friday", Selected = false, _ListTimes = ListTimes},
new RepeatMonthly(){Day="Every Saturday", Selected = false, _ListTimes = ListTimes},
new RepeatMonthly(){Day="Every Sunday", Selected = false, _ListTimes = ListTimes}
};
source = new ObservableCollection<PetProfile>();
}
My Xaml:
<CollectionView x:Name="RepeatCollectionView" HorizontalOptions="Center" ItemsSource="{Binding DayList}" HeightRequest="350">
<CollectionView.ItemTemplate>
<DataTemplate>
<Grid Padding="0" RowDefinitions="25, 20" ColumnDefinitions="*,*">
<Label Text="{Binding Day}" FontAttributes="Bold" FontSize="Medium"
Margin="5,0"
VerticalTextAlignment="Center" HorizontalTextAlignment="Start"/>
<CheckBox x:Name="SelectDayCheckBox" Grid.Row="0" HorizontalOptions="End" IsChecked="{Binding Selected, Mode=TwoWay}" BindingContext="{Binding .}" CheckedChanged="SelectDayCheckBox_CheckedChanged"/>
<Picker x:Name="SelectTimeOfWalkPicker" Title="--Select Walk Start Time--" Grid.Column="1" ItemsSource="{Binding _ListTimes}" ItemDisplayBinding="{Binding Value}" VerticalTextAlignment="Center" HorizontalTextAlignment="Center" SelectedItem="{Binding SelectedTimes}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
my .cs page:
private async void SubmitBtn_Clicked(object sender, EventArgs e)
{
foreach (RepeatMonthly booking in (BindingContext as CreateWeeklyScheduleViewModel).DayList)
{
if (booking.Selected)
{
await passtoDataBase(booking.Day, "where time variable goes");
}
}
await DisplayAlert("Success", "Booked Successfully", "OK");
await Shell.Current.GoToAsync($"//{nameof(MyBookingsPage)}");
}
properties of WalkTimes object:
public class WalkTimes
{
public int Key { get; set; }
public string Value { get; set; }
}

Two search bar Xamarin Forms

I have a question here (I imagine it is for beginners: P).
I have a listview and a searchbar already working. I can make the right filter.
Then my question comes in, this listview has more than one column.
I can't think of a way to make 2 complementary filters.
For example:
Filter column 1 and then filter the result of that initial filter by column 2 (with another searchbar), that is, a filter on top of another filter.
My ListViewItem is like this with the filter:
C#
void InitList()
{
Items = new List<ListViewItem>
{
new ListViewItem { Name = "Guilherme", Bairro = "BOTAFOGO"},
new ListViewItem { Name = "João", Bairro = "FLAMENGO"},
new ListViewItem { Name = "Maria", Bairro = "CENTRO"}
}
}
void InitSearchBarBloco()
{
sb_search_bloco.TextChanged += (s, e) => FilterItem(sb_search_bloco.Text);
sb_search_bloco.SearchButtonPressed += (s, e) =>
FilterItem(sb_search_bloco.Text);
}
private void FilterItem(string filter)
{
exampleListView.BeginRefresh();
if (string.IsNullOrWhiteSpace(filter))
{
exampleListView.ItemsSource = Items;
}
else
{
exampleListView.ItemsSource = Items.Where(x => x.Name.ToLower().Contains(filter.ToLower()));
}
exampleListView.EndRefresh();
}
XAML
<SearchBar x:Name="sb_search_bloco" Placeholder="Nome..." />
<ListView x:Name="exampleListView" RowHeight="22" SelectedItem="{Binding Name}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell >
<Grid>
<Label Text="{Binding Name}" LineBreakMode="TailTruncation" />
<Label Grid.Column="1" Text="{Binding Bairro}" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
With this structure I can implement this ... "filtrate filter"?
thanks
I guess the below code should do fine for you. You just need to alter your linq code inside the FilterItem(string filter) method to achieve your requirement.
Note:
I have used OR condition inside the where clause to search if the enter text is available in both Name and Bairro. However, you can modify the condition as you require.
private void FilterItem(string filter)
{
exampleListView.BeginRefresh();
if (string.IsNullOrWhiteSpace(filter))
{
exampleListView.ItemsSource = Items;
}
else
{
//Alter the condition like below or based on requirement to achieve the desired result.
exampleListView.ItemsSource = Items.Where(x => x.Name.ToLower().Contains(filter.ToLower()) || x.Bairro.ToLower().Contains(filter.ToLower()));
}
exampleListView.EndRefresh();
}
I make a sample code for your referece.
xmal:
<StackLayout>
<StackLayout>
<SearchBar x:Name="sb_search_bloco" Placeholder="Nome..." TextChanged="sb_search_bloco_TextChanged"/>
<SearchBar x:Name="searchBar2" Margin="0,10" TextChanged="searchBar2_TextChanged" />
</StackLayout>
<ListView
x:Name="exampleListView"
RowHeight="22"
ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid>
<Label LineBreakMode="TailTruncation" Text="{Binding Name}" />
<Label Grid.Column="1" Text="{Binding Bairro}" />
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
ListViewItem.cs
public class ListViewItem
{
public string Name { get; set; }
public string Bairro { get; set; }
}
MainPage.xml.cs
public partial class MainPage : ContentPage
{
public List<ListViewItem> Items { get; set; }
public MainPage()
{
InitializeComponent();
Items = new List<ListViewItem>
{
new ListViewItem { Name = "AAAA", Bairro = "BBCFS"},
new ListViewItem { Name = "ABBB", Bairro = "SSDCA"},
new ListViewItem { Name = "AAAA", Bairro = "AAAD"},
new ListViewItem { Name = "CCCC", Bairro = "SSS"},
new ListViewItem { Name = "DAAB", Bairro = "CCC"},
new ListViewItem { Name = "DDDC", Bairro = "QWDAS"}
};
this.BindingContext = this;
}
private void sb_search_bloco_TextChanged(object sender, TextChangedEventArgs e)
{
exampleListView.ItemsSource = FilterItem1(e.NewTextValue);
}
IEnumerable<ListViewItem> FilterItem1(string filter = null)
{
if (string.IsNullOrEmpty(filter))
return Items;
return Items.Where(p => p.Name.StartsWith(filter));
}
private void searchBar2_TextChanged(object sender, TextChangedEventArgs e)
{
exampleListView.ItemsSource = FilterItem2(e.NewTextValue);
}
IEnumerable<ListViewItem> FilterItem2(string filter = null)
{
if (string.IsNullOrEmpty(filter))
return Items;
return Items.Where(p => p.Bairro.StartsWith(filter));
}
}

dynamically add button or image from code behind to pivotitem

How can I add many Button to the grid for the first Item and and textbox to another grid in the second item dynamically?
NOT XAML
Ican't find name GridItem - name not exist in code behind :(
I tried to find Visual Tree Helper :(
PivotItem pivotVHT = (PivotItem)mainSecondPivot.SelectedItem;
foreach (var element in VisualTreeHelper.FindElementsInHostCoordinates(new Rect(20, 0, 480, 700), pivotVHT))
{
if (element is TextBlock)
{
Debug.WriteLine("{0}", ((TextBlock)element).Text);
TextBlock test = ((TextBlock)element);
test.Text = "TEST";
}
}
VisualTreeHelper changing the text only mainFirstPivot, visual tree helper does not see mainSecondPivot
XAML:
<controls:Pivot Title="Photo Gallery" Name="mainSecondPivot" >
<controls:Pivot.HeaderTemplate>
<DataTemplate>
<Grid
x:Name="PivitGrid"
>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Image
Name="PivotImageGalery"
Source="{Binding imgSrc}"
>
</Image>
<TextBlock
x:Name="TextBlockPivot"
Text="{Binding textBlockPivotName}"
>
</TextBlock>
</Grid>
</DataTemplate>
</controls:Pivot.HeaderTemplate>
<controls:Pivot.ItemTemplate>
<DataTemplate>
<ScrollViewer
Name="SVName"
Width="Auto"
VerticalScrollBarVisibility ="Hidden"
HorizontalScrollBarVisibility="Disabled"
>
<Grid
x:Name="GridItem"
>
**HERE**
</Grid>
</ScrollViewer>
</DataTemplate>
</controls:Pivot.ItemTemplate>
</controls:Pivot>
C#
public static class SelectedIndex
{
public static int SelectedIndexInt = 0;// OR SOME NUMBER
}
public class IListPivot
{
public ImageSource imgSrc { get; set; }
public String textBlockPivotName { get; set; }
}
public secondPage()
{
InitializeComponent();
IList<IListPivot> PivotList = new List<IListPivot>();
for (z = 0; z <= 7; z++)
{
PivotList.Add(new IListPivot()
{
imgSrc = new System.Windows.Media.Imaging.BitmapImage(new Uri("URI", UriKind.Relative)),
textBlockPivotName = "TEXT"
});
}
mainSecondPivot.ItemsSource = PivotList;
mainSecondPivot.Loaded += new RoutedEventHandler (PivotLoaded);
mainSecondPivot.SelectedIndex = SelectedIndex.SelectedIndexInt
}
public void PivotLoaded(object sender, EventArgs e)
{
PivotItem pivotItemVHT = (PivotItem)mainSecondPivot.ItemContainerGenerator.ContainerFromIndex(SelectedIndex.SelectedIndexInt);
var root = VisualTreeHelper.GetChild(((VisualTreeHelper.GetChild(pivotItemVHT, 0) as Grid).Children[0] as ContentPresenter), 0) as FrameworkElement;
Debug.WriteLine(" root " + root);
Debug.WriteLine(" root Name " + root.Name);
ScrollViewer scr = (ScrollViewer)root;
TextBox BoxText1 = new TextBox();
BoxText1.Text = a.ToString();
scr.Content = BoxText1;
}
add only to one from everyone items
HELP
Here is how to retrieve a named element from inside a PivotItem:
public FrameworkElement GetPivotElement(int pivotIndex, string name)
{
var pivot = mainSecondPivot.ItemContainerGenerator.ContainerFromIndex(pivotIndex);
var root = VisualTreeHelper.GetChild(((VisualTreeHelper.GetChild(pivot, 0) as Grid).Children[0] as ContentPresenter), 0) as FrameworkElement;
return root.FindName(name);
}

parse xml and binding data to listbox

It's very simple task, but I struggle for hours.
I have parse xml from web sources and bind them to listbox. Now I want to make an index for each items binded to list box, something like this:
1.Title 2.Title 3.Title
Author Author Author
Date Date Date
Here is what I have so far:
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="stkPnl" Orientation="Horizontal" Margin="15,0" MouseEnter="stkPnl_MouseEnter" MouseLeave="stkPnl_MouseLeave">
<Image x:Name="imageAV" Source="{Binding avlink}" Height="80" Width="80"
Stretch="UniformToFill" MouseLeftButtonUp="imageAV_MouseLeftButtonUp" ImageFailed="imageAV_ImageFailed"/>
<StackPanel Orientation="Vertical" Margin="10,0,0,0" MouseLeftButtonUp="StackPanel_MouseLeftButtonUp">
<TextBlock Text="{Binding nickname}" Width="Auto" />
<TextBlock Text="{Binding track}" FontWeight="Bold" Width="Auto"/>
<TextBlock Text="{Binding artist}" Width="Auto"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
and MainPage.xaml.cs
private void DoWebClient()
{
var webClient = new WebClient();
webClient.OpenReadAsync(new Uri("http://music.mobion.vn/api/v1/music/userstop?devid="));
webClient.OpenReadCompleted += new OpenReadCompletedEventHandler(webClient_OpenReadCompleted);
}
void webClient_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
using (var reader = new StreamReader(e.Result))
{
string s = reader.ReadToEnd();
Stream str = e.Result;
str.Position = 0;
XDocument xdoc = XDocument.Load(str);
var data = from query in xdoc.Descendants("user")
select new mobion
{
avlink = (string)query.Element("user_info").Element("avlink"),
nickname = (string)query.Element("user_info").Element("nickname"),
track = (string)query.Element("track"),
artist = (string)query.Element("artist"),
};
listBox.ItemsSource = data;
}
}
As you see I only have nickname, track and artist, if I want to add an index that increase for each listboxItem, how can I do that?
Thank you for reading this question.
I know it's ugly, but it's an idea: Create a wrapper class for around your mobion class:
public class mobionWrapper : mobion
{
public int Index { get; set; }
}
Instead of selecting mobion instances you select mobionWrapper instances:
var data = from query in xdoc.Descendants("user")
select new mobionWrapper
{
avlink = (string)query.Element("user_info").Element("avlink"),
nickname = (string)query.Element("user_info").Element("nickname"),
track = (string)query.Element("track"),
artist = (string)query.Element("artist"),
};
After binding the your data, set the Index property of your wrapper class:
listBox.ItemsSource = data;
for(int i = 0; i < listBox.Items.Count; i++)
{
var item = listBox.Items[i] as mobionWrapper;
item.Index = i + 1;
}
Now you need a new TextBlock and bind it to the Index property:
<TextBlock Text="{Binding Index}" Width="Auto" />
Worked for me. Keep in mind that the index might be invalid after sorting or filtering the data display.
Assuming you've an appropriate field in you mobion class (which you can bind to in the view), you can use an incrementing counter to populate this field as the document is iterated over.
int[] counter = { 1 };
var data = from query in xdoc.Descendants("user")
select new mobion
{
index = counter[0]++,
avlink = (string)query.Element("user_info").Element("avlink"),
nickname = (string)query.Element("user_info").Element("nickname"),
track = (string)query.Element("track"),
artist = (string)query.Element("artist"),
};
Note that counter is an array of int rather than just an int to prevent accessing a modified closure.

Resources