Data Template Button created in XamlReader don't firing events - c#

I'm trying to create a ListView Page that works for every object in my app where I can show all the objects received by parameter from a List<object> where I don't know which object the page will have to handle, I also receive the fields to show as parameters in a List <string, string> where the first string is the type of the field (header/subheader/image) and for that, I had to use XAML Reader so I could create the data bindings in the code behind.
Here's my code:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var param = (Dictionary<string, object>)e.Parameter;
_ItemsList = (List<object>)param["itemsList"];
_Fields = (Dictionary<string, string>)param["fields"];
_DetailsPage = (Type)param["detailPage"];
EditCommand = new UniversalListViewCommand();
ObjListView.ItemsSource = UniversalAutoSuggestBox.ItemsSource = _ItemsList;
MainStackPanel.DataContext = this;
ObjListView.ItemTemplate = NormalItemTemplate = (DataTemplate)XamlReader.Load(GenerateDataTemplate("NormalItemTemplate"));
SelectedItemTemplate = (DataTemplate)XamlReader.Load(GenerateDataTemplate("SelectedItemTemplate"));
}
public string GenerateDataTemplate(string param)
{
StringBuilder sbTemp = new();
//Data Template definition
if (param == "NormalItemTemplate") sbTemp.Append("<DataTemplate x:Key=\"NormalItemTemplate\" ");
if (param == "SelectedItemTemplate") sbTemp.Append("<DataTemplate x:Key=\"SelectedItemTemplate\" ");
//sbTemp.Append("x:Class = \"xizPrototipo_0_2.Pages.UniversalListViewPage\" ");
sbTemp.Append(#"xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" ");
sbTemp.Append(#"xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">");
//Grid definition
sbTemp.Append("<Grid MinWidth=\"350\" Margin=\"5\">");
//Grid row definition
sbTemp.Append("<Grid.RowDefinitions>");
sbTemp.Append("<RowDefinition Height = \"*\" />");
sbTemp.Append("<RowDefinition Height = \"*\" />");
sbTemp.Append("<RowDefinition Height = \"*\" />");
sbTemp.Append("</Grid.RowDefinitions>");
//Grid column definition
sbTemp.Append("<Grid.ColumnDefinitions>");
sbTemp.Append("<ColumnDefinition Width = \"Auto\"/>");
sbTemp.Append("<ColumnDefinition Width = \"*\" />");
sbTemp.Append("</Grid.ColumnDefinitions>");
//Ellipse definition
sbTemp.Append("<Ellipse Grid.Row=\"0\" Grid.RowSpan=\"2\" Width = \"32\" Height = \"32\" Margin = \"6\" VerticalAlignment = \"Center\" HorizontalAlignment = \"Center\">");
sbTemp.Append("<Ellipse.Fill> <ImageBrush x:Name =\"LogoImage\" ImageSource = \"{Binding " + _Fields["Image"] + "}\" /> </Ellipse.Fill> ");
sbTemp.Append("</Ellipse>");
//Header Text Block Definition
sbTemp.Append("<TextBlock Grid.Row = \"0\" Grid.Column = \"1\" Text = \"{Binding " + _Fields["Header"] + "}\" Style = \"{ThemeResource BaseTextBlockStyle}\" Margin = \"12,6,0,0\"/> ");
//Subheader Text Block Definition
sbTemp.Append("<TextBlock Grid.Row=\"1\" Grid.Column = \"1\" Text = \"{Binding " + _Fields["Subheader"] + "}\" Style = \"{ThemeResource BodyTextBlockStyle}\" Margin = \"12,0,0,6\" /> ");
//Button (case Selected)
if (param == "SelectedItemTemplate")
{
sbTemp.Append("<StackPanel Grid.Row=\"2\" Grid.Column=\"1\" Orientation =\"Horizontal\" HorizontalAlignment =\"Right\" Spacing = \"5\" > ");
sbTemp.Append("<Button x:Name=\"EditButton\" Content=\"Editar\" Click=\"AlterButton_Click\" />");
sbTemp.Append("</StackPanel>");
}
//Grid end
sbTemp.Append("</Grid>");
//DataTemplate end
sbTemp.Append("</DataTemplate>");
return sbTemp.ToString();
}
With this method I could get the outcome that I wanted but when I click the button created in the following line it don't fires any event:
sbTemp.Append("<Button x:Name=\"EditButton\" Content=\"Editar\" Click=\"AlterButton_Click\" />");
I also tried using commands, Data Template selector and still I couldn't do anything with that button.
Does anyone know how can I put that button to work?
(Yes, I have the event on the button created and a function to change the Data Template to show the button.)

Data Template Button created in XamlReader don't firing events
I am afraid it will not respond to the click event handler created from the string xaml. Because the click event registration process will be saved in the xxpage.g.cs file during pre-compilation. However, if you load the data template from the string runtime. It will not register for this event.
So the better way is binding command for button.
For example
public MainPage()
{
this.InitializeComponent();
DataTemplate itmeTemplate = (DataTemplate)XamlReader.Load(
#"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
<Button Command='{Binding BtnClickCommand}' Content='Editor' />
</DataTemplate>");
TestListView.ItemTemplate = itmeTemplate;
NumbersList = new ObservableCollection<Item>();
for (int i = 0; i < 50; i++)
{
var item = new Item ();
NumbersList.Add(item);
}
TestListView.ItemsSource = NumbersList;
}
public ObservableCollection<Item> NumbersList { get; set; }
Model
public class Item : INotifyPropertyChanged
{
public ICommand BtnClickCommand
{
get
{
return new RelayCommand(() =>
{
});
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Xaml Code
<Grid x:Name="RootGrid">
<ListView
x:Name="TestListView"
ItemsSource="{x:Bind NumbersList}"
Visibility="Visible">
</ListView>
</Grid>
Update 1
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
DataTemplate itmeTemplate = (DataTemplate)XamlReader.Load(
#"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
<Button Command='{Binding DataContext.BtnClickCommand, ElementName=TestListView}' Content='Editor' />
</DataTemplate>");
TestListView.ItemTemplate = itmeTemplate;
NumbersList = new ObservableCollection<Item>();
for (int i = 0; i < 50; i++)
{
var item = new Item ();
NumbersList.Add(item);
}
TestListView.ItemsSource = NumbersList;
}
public ICommand BtnClickCommand
{
get
{
return new RelayCommand(async () =>
{
});
}
}

Related

ListView not updating when orientation changed

I have a DataGrid added programmatically in my App. I have a event which is called when the MainDisplayInfo gets changed. In this event I call my function to create the grid.
Grid grid = this.dataGrid.Children[0] as Grid; // the header
ContentView view = this.dataGrid.Children[1] as ContentView;
ListView lv = this.dataGrid.Children[2] as ListView; // the body of the datagrid
lv.ItemsSource = null;
lv.ItemsSource = viewModel.Statistiks;
I'm adding my Header and my Columns like this:
foreach (var item in zip)
{
if (IsPortrait)
{
cc.Add(new DataGridColumn() { Title = item.Headers, PropertyName = item.Tables, Width = GridLength.Star, HorizontalContentAlignment = LayoutOptions.CenterAndExpand });
}
else
cc.Add(new DataGridColumn() { Title = item.Headers, PropertyName = item.Tables, Width = GridLength.Star, HorizontalContentAlignment = LayoutOptions.CenterAndExpand });
}
My Itemssource Collection looks like this:
private ObservableCollection<StatistikModel> statistiks;
public ObservableCollection<StatistikModel> Statistiks
{
get { return statistiks; }
set { statistiks = value; OnPropertyChanged("Statistiks"); }
}
But when the orientation gets changed only the Header changes correctly but not the listview.

c# wpf - ListView.ScrollIntoView(LastItem) does not work properly

like some other people here i have a ListView (updated via binding in a GridView).
I want to keep the last inserted Item in the View. So i tried
LView.ScrollIntoView(LView.Items[LView.Items.Count - 1]);
This is working almost fine. Altough the first item which would have to be scrolled into view is only shown like 80% of its whole row (depending on how high i define the whole ListView, i almost got 100%).
The real problem is that the following items which should get scrolled into view are not shown. It is also noticable at the Scrollbar itself which is not at the bottom.
Last Item is not shown
Here is the code of my MainWindow.
public partial class MainWindow : Window
{
private InterfaceCtrl ICtrl;
private ListView LView;
public MainWindow()
{
InitializeComponent();
this.ICtrl = new InterfaceCtrl();
this.ICtrl.ProgressCollection.CollectionChanged += this.CollectionChanged;
Grid MainGrid = new Grid();
this.Content = MainGrid;
GridView gv = new GridView();
Binding StartTimeStampBinding = new Binding() { Path = new PropertyPath("StartTS"), Mode = BindingMode.OneWay, StringFormat = "dd.MM.yyyy - HH:mm:ss.fff" };
GridViewColumn gvTCStartTS = new GridViewColumn() { Header = "Time", Width = 150.00, DisplayMemberBinding = StartTimeStampBinding };
gv.Columns.Add(gvTCStartTS);
LView = new ListView() { Height = 192, Width = 250, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Top, View = gv, ItemsSource = this.ICtrl.ProgressCollection };
MainGrid.Children.Add(LView);
ICtrl.StartMyThread();
}
private void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Application.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new System.Action(delegate ()
{
if (LView != null && LView.Items.Count > 0)
{
LView.UpdateLayout();
//LView.ScrollIntoView(LView.Items[LView.Items.Count - 1]);
LView.SelectedIndex = LView.Items.Count;
LView.ScrollIntoView(LView.SelectedItem);
}
}));
}
}
Thank you.
EDIT:
It seemed to be a timing problem, although all the wanted data was in the LView at the right time i tried a workaround with a Textbox bound to the Timestamp.
TextBox tb = new TextBox(); // { Width = 250, Height = 28, Margin= new Thickness(10,100,1,0)};
tb.SetBinding( TextBox.TextProperty , new Binding("LastMsgTimestamp") { Source = this.ICtrl, Mode = BindingMode.OneWay, StringFormat = "dd.MM.yyyy - HH:mm:ss.fff" });
tb.TextChanged += this.UpdateScrollbar;
tb.Visibility = Visibility.Hidden;
It seems to me like there is a timing issue within the binding to the LView and the fired Event of the ObservableCollection. This also includes the PropertyChanged of the ObservableCollection.
I tried the events TargetUpdated and SoruceUpdated directly within LView but those didn't came up at all.
You could try to call any of the ScrollToBottom() or ScrollToVerticalOffset() methods of the ListView's internal ScrollViewer element:
private void CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Application.Current.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new System.Action(delegate ()
{
if (LView != null && LView.Items.Count > 0)
{
LView.UpdateLayout();
ScrollViewer sv = GetChildOfType<ScrollViewer>(LView);
if (sv != null)
sv.ScrollToBottom();
LView.SelectedIndex = LView.Items.Count;
LView.ScrollIntoView(LView.SelectedItem);
}
}));
}
private static T GetChildOfType<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj == null)
return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null)
return result;
}
return null;
}
I have made the following sample. You could try to call ScrollToBottom in inner ScrollViewer as #mm8 points out. Nevertheless when saw the answer I was already making my sample, so here it is:
Codebehind
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace ListViewScroll
{
public partial class MainWindow : Window
{
public ObservableCollection<string> Names { get; set; }
public MainWindow()
{
InitializeComponent();
Names = new ObservableCollection<string>();
ListView.ItemsSource = Names;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Names.Add("Some Name" + ++someInt);
// Get the border of the listview (first child of a listview)
var border = VisualTreeHelper.GetChild(ListView, 0) as Decorator;
// Get scrollviewer
var scrollViewer = border.Child as ScrollViewer;
scrollViewer.ScrollToBottom();
}
private static int someInt;
}
}
XAML
<Window x:Class="ListViewScroll.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<ListView Grid.Row="0" Name="ListView"/>
<Button Content="Add" FontSize="20" Grid.Row="1"
Click="ButtonBase_OnClick"/>
</Grid>
</Window>
In this case I am handling the scrolling in the button click event but you may change this to fit your requirements
It works, I have tested.
Hope this helps

Can't navigation between xaml in frame?

I have got issues . Why did not it navigate to other xaml? Where is wrong?
So, I was trying to make it that can navigated between two or more xaml in a frame.
Here is link : https://github.com/Englbach/MutiViewInRootPage
<SplitView.Content>
<!-- OnNavigatingToPage we synchronize the selected item in the nav menu with the current page.
OnNavigatedToPage we move keyboard focus to the first item on the page after it's loaded. -->
<Frame x:Name="AppShellFrame">
<Frame.ContentTransitions>
<TransitionCollection>
<NavigationThemeTransition>
<NavigationThemeTransition.DefaultNavigationTransitionInfo>
<EntranceNavigationTransitionInfo />
</NavigationThemeTransition.DefaultNavigationTransitionInfo>
</NavigationThemeTransition>
</TransitionCollection>
</Frame.ContentTransitions>
</Frame>
</SplitView.Content>
public static AppShell Current = null;
public List<NavMenuItem> NavList { get; } = new List<NavMenuItem>(new[]
{
new NavMenuItem()
{
Symbol = Symbol.Add,
Label = "Add feed",
DestPage = typeof(RootPages),
Arguments = typeof(AddFeedView)
},
new NavMenuItem()
{
Symbol = Symbol.Edit,
Label = "Edit feeds",
DestPage = typeof(RootPages),
Arguments = typeof(EditFeedView)
}
});
public AppShell()
{
this.InitializeComponent();
Current = this;
}
private void NavMenuList_ItemInvoked(object sender, ListViewItem e)
{
NavMenuList.SelectedIndex = -1;
var item = (NavMenuItem)((NavMenuListView)sender).ItemFromContainer(e);
if(item!=null)
{
AppFrame.Navigate(typeof(RootPages), item.Arguments);
}
}
You probably followed the official Navigation menu (XAML) sample to design your layout.
There are two tiny problems in your demo.
Each time the items in the NavMenuList is clicked, the NavMenuList_ItemInvoked event in the AppShell.xaml.cs should be triggered. And in this event, you navigated again and again to your RootPages, and together pass the navigation parameter(item.Arguments) to the RootPages like this AppFrame.Navigate(typeof(RootPages), item.Arguments);, and the argument is actually your destination.
You can modify the code here like this:
private void NavMenuList_ItemInvoked(object sender, ListViewItem e)
{
//NavMenuList.SelectedIndex = -1;
var item = (NavMenuItem)((NavMenuListView)sender).ItemFromContainer(e);
if (item != null)
{
//AppFrame.Navigate(typeof(RootPages), item.Arguments);
if (item.DestPage != null &&
item.DestPage != this.AppFrame.CurrentSourcePageType)
{
this.AppFrame.Navigate(item.DestPage, item.Arguments);
}
}
}
Then here comes the second problem, as I said, the Arguments should be navigation parameter, for example, I navigate to AddFeedView page and I want to send a message "balalala", then we can code like this: AppFrame.Navigate(typeof(AddFeedView), "balalala");. This means, you confused with your DestPage and Arguments.
You can modify your NavList like this:
public List<NavMenuItem> NavList { get; } = new List<NavMenuItem>(new[]
{
new NavMenuItem()
{
Symbol = Symbol.Add,
Label = "Add feed",
DestPage = typeof(AddFeedView),
Arguments = typeof(RootPages)
},
new NavMenuItem()
{
Symbol = Symbol.Edit,
Label = "Edit feeds",
DestPage = typeof(EditFeedView),
Arguments = typeof(RootPages)
}
});
In additional, if you want your AppFrame to navigate to the RootPages at first by default, you can code like this:
public AppShell()
{
this.InitializeComponent();
Current = this;
AppFrame.Navigate(typeof(RootPages));
}

Displaying a Xamarin Forms StackList doesn't render properly

While learning how to use Xamarin Forms and StackLayouts I'm having trouble rendering the items.
I'm trying to bind a collection of somethings to a StackLayout. The binding (more or less) works. The visual render is wrong.
For this question, I've picked some random thing (in this case, credit cards) to highlight my experimenting.
This is what I'm rending:
Notice how each custom row looks to be fixed height's or something?
Show me the code! Ok, so this is what I've done:
First, the page:
public class MyPage : ContentPage
{
protected override void OnAppearing()
{
var creditCards = new SomeFakeListOfCreditCards();
var stackLayout = new CreditCardStackLayout(creditCards);
Contet = stackLayout;
}
}
Now for the custom control...
public class CreditCardStackLayout : StackLayout
{
public CreditCardStackLayout(IEnumerable<CreditCard> creditCards)
{
// Note: credit cards can be null (because we don't have any).
CreateContent(creditCards);
}
private void CreateContent(IEnumerable<CreditCard> creditCards)
{
VerticalOptions = LayoutOptions.Center;
Children.Add(TitleLabel);
if (creditCards == null)
{
Children.Add(NoCreditCardLabel);
}
else
{
Children.Add(CreateCreateCardList(creditCards));
}
}
private static ListView CreateCreateCardList(IEnumerable<CreditCard> creditCards)
{
if (creditCards == null)
{
throw new ArgumentNullException("creditCards");
}
// Project the credit cards into a list view model.
var viewModel = creditCards.Select(x => new CreditCardViewModel(x));
return new ListView
{
ItemsSource = viewModel,
ItemTemplate = new DataTemplate(typeof(CreditCardViewCell))
};
}
private Label TitleLabel
{
get
{
return new Label
{
HorizontalOptions = LayoutOptions.Center,
Text = "A list of credit cards"
};
}
}
private Label NoCreditCardLabel
{
get
{
return new Label
{
Text = "You have no real credit cards on file."
};
}
}
}
So far so good.. Now for the binding..
public class CreditCardViewCell : ViewCell
{
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
var stackLayout = new StackLayout
{
BackgroundColor = new Color(46, 81, 163),
VerticalOptions = LayoutOptions.Center,
Children =
{
NameLabel,
TypeImage,
NumberLabel,
ExpiresOnLabel,
RemoveButton
}
};
View = stackLayout;
}
private Label NameLabel
{
get
{
var label = new Label
{
FontSize = 14
};
label.SetBinding(Label.TextProperty, "Name");
return label;
}
}
private Label NumberLabel
{
get
{
var label = new Label
{
FontSize = 14
};
label.SetBinding(Label.TextProperty, "Number");
return label;
}
}
private Label ExpiresOnLabel
{
get
{
var label = new Label
{
FontSize = 14
};
label.SetBinding(Label.TextProperty, "ExpiresOn");
return label;
}
}
private Image TypeImage
{
get
{
var creditCardModel = (CreditCardViewModel)BindingContext;
string fileName = null;
switch (creditCardModel.Type)
{
case "Visa":
fileName = "visa.png";
break;
default:
fileName = "mastercard.png";
break;
}
var image = new Image
{
Source = ImageSource.FromFile("Icons/" + fileName)
};
return image;
}
}
private Button RemoveButton
{
get
{
var button = new Button
{
//Image = "Icons/remove.png"
Text = "Remove"
};
button.Clicked += async (object sender, EventArgs e) =>
{
// TODO when I figure out how to do this stuff.
int i = 0;
i++;
};
return button;
}
}
}
}
Also, please do not suggest going to a XAML file, I'm trying to learn this stuff programmatically.
Side thought: I wish I could turn on borders so I can see how things are laid out.
Use ListView's HasUnevenRows property. Change this:
return new ListView
{
ItemsSource = viewModel,
ItemTemplate = new DataTemplate(typeof(CreditCardViewCell))
};
to:
return new ListView
{
HasUnevenRows = true,
ItemsSource = viewModel,
ItemTemplate = new DataTemplate(typeof(CreditCardViewCell))
};
By default Xamarin.Forms ListView row size is fixed and set to the same value for every row (RowHeight property). With this configuration row auto-sizing won't work, but it also gives best ListView performance.
You can auto-size rows by setting ListView's HasUnevenRows property to true. It allows modifying ViewCell size by setting ViewCell's RowHeight property (eg. in OnBindingContextChanged override). In newer Xamarin.Forms versions it also sizes ViewCells automatically (on older it worked only on Android).

How to get button name added on UniformGrid runtime?

Get Button name added on UniformGrid during run time.
My code is:
for (int i = 1; i <= no; ++i)
{
Button button = new Button()
{
Content = i,
Tag = i,
Background = Brushes.White,
Height = 30,
Width = 30,
Name="A" + i.ToString()
};
string name=button.Name;
button.Click += new RoutedEventHandler(button_Click);
this.grid1.Children.Add(button);
if (name.Equals("A1"))
{
button.Background = Brushes.White;
}
}
My requirement is to get Button name in other function:
private void Sum()
{
}
Here is my XAML:
<UniformGrid x:Name="grid1" Margin="816,115,96,354" Grid.Column="1" />
It isn't clear what is the ultimate goal here. But you can loop through all child of UniformGrid which type is Button to get name of each button and take action accordingly :
foreach (var btn in grid.Children.OfType<Button>())
{
var btnName = btn.Name;
//take action according to button name
}

Categories