Add multiple labels in Xamarin Forms - c#

I have the following label:
<Label Text="{x:Static local:FontAwesome.FACheck}" FontFamily="FontAwesome" TextColor="Green"/>
And an event on button:
correctButton.Clicked += (sender, e) =>
{
App.DB.IncrementScore();
};
What this does is everytime I click on a button my score is incremented by 1. What I wanted to do is also increment the number of Labels depending on the number of score. See the attached image below:
Anyone has any idea how I could achieve this?

There are different solutions to this question. Be sure to read through all of them before choosing one - my favorite (the simplest one) is listed all the way down...
Approach #1:
As several people have suggested, you can create some collection control (I'll come to that in a moment), define an ObservableCollection within a ViewModel, set the Page's Binding Context to an instance of that ViewModel, and add items to the collection on button click:
public class MyViewModel()
{
public ObservableCollection<int> Items { get; set; } = new ObservableCollection<int>();
}
private MyViewModel _viewModel = new MyViewModel();
public MainPage()
{
InitializeComponent();
BindingContext = _viewModel;
}
correctButton.Clicked += (sender, e) =>
{
App.DB.IncrementScore();
_viewModel.Items.Add(0);
};
The type of ObservableCollection actually does not matter, since we are using the same static ItemTemplate for all items:
<ContentPage.Resources>
<DataTemplate x:Key="ScoreItemTemplate">
<ViewCell>
<ViewCell.View>
<Label Text="{x:Static local:FontAwesome.FACheck}" FontFamily="FontAwesome" TextColor="Green"/>
</ViewCell.View>
</ViewCell>
</DataTemplate>
</ContentPage.Resources>
The main problem with this approach is that Xamarin.Forms ListView can not display its items horizontally. This means, you'd need to download one of the available HorizontalListView Nuget packages out there, or use the built-in (only in Xamarin.Forms 2.3 and above!) CarouselView:
<CarouselView ItemTemplate="{StaticResource ScoreItemTemplate}" ItemsSource="{Binding Items}"/>
Then, you'll might wish to spend some time removing all the visual effects for swiping through the carousel, and for selecting items if you choose to use a horizontal ListView...
Instead, there are two alternative solutions that involve less effort:
Approach #2:
Obviously, the simple approach would be to create the "template" label in code:
private Label CreateScoreLabel()
{
return new Label {Text = FontAwesome.FACheck, TextColor = Color.Green, FontFamily = "FontAwesome"};
}
...add a horizontal StackLayout to the page:
<StackLayout Orientation="Horizontal" x:Name="LabelStack"/>
...and add new labels the hard way:
correctButton.Clicked += (sender, e) =>
{
App.DB.IncrementScore();
LabelStack.Children.Add(CreateScoreLabel());
};
However, all this seems rather hacky for just creating a list of green check marks. This leads us to...
...approach #3:
Technically, this isn't exactly what you asked for (increment the number of Labels), but according to your screen shot it might fulfill your needs in a much simpler way.
Remove the existing label's text (as it shall display nothing at startup), and instead give it a unique name:
<Label x:Name="ScoreLabel" FontFamily="FontAwesome" TextColor="Green"/>
Now, define a simple extension method for the string type that repeats a given string for a certain number of times:
public static class StringExtensions
{
public static string Repeat(this string input, int num)
{
return String.Concat(Enumerable.Repeat(input, num));
}
}
(There are multiple ways to make this repeat function as performant as possible, I simply chose the simplest one-liner available, search StackOverflow for detailed discussions...)
You can now work with the single Label control defined in XAML, and just assign several check marks to it on button click:
correctButton.Clicked += (sender, e) =>
{
App.DB.IncrementScore();
ScoreLabel.Text = FontAwesome.FACheck.Repeat(App.DB.Score); // replace parameter by whatever method allows you to access the current score number
};
Of course, this approach can also be adapted to follow the MMVM way, by simply using a public bindable string property instead of setting the label's Text attribute directly, but for this I'd advise you to take a look at a beginner's MVVM tutorial.

Simply insert new label code in button click event which will add new children in your score stack.
cs page Code:-
button.Clicked += (sender, e) =>
{
App.DB.IncrementScore();
lblStack.Children.Add(new Label {Text = FontAwesome.FACheck, TextColor = Color.Green, FontFamily = "FontAwesome"});
};
xamal code
<StackLayout Orientation="Horizontal" x:Name="lblStack"/>

If the score has a maximum just add the max number of labels and bind their visibility.
Or you can make a listview of labels and bind the source to a collection that you increment when required.

Note: I'm not sure if this answer is correct, but it's too long for a comment. Just test it and if it doesn't work I will delete this answer
You can add controls programmatically. Keep in mind that you need to add a Label on your MainThread, because it's a change to the UI. Add this to your Clicked event:
correctButton.Clicked += (sender, e) =>
{
App.DB.IncrementScore();
Device.BeginInvokeOnMainThread(() => // On MainThread because it's a change in your UI
{
Label label = new Label();
label.Text = "{x:Static local:FontAwesome.FACheck}"; // Not sure if this is right syntax...
label.FontFamily = "FontAwesome";
label.TextColor = Color.Green;
stackPanel.Children.Add(label);
});
};
In case you have a Grid, you can set Grid.Row and Grid.Column with the SetValue method of a Control:
label.SetValue(Grid.RowProperty, 1);
label.SetValue(Grid.RowProperty, 2);

Related

How to add buttons dynamically with a MessagingCenter in Xamarin.Forms MVVM

I want to add buttons in a dynamic view, for example in some cases I will add 2 buttons and in certain opportunities I will add 4 buttons, but I want to generalize all this logic in a view to which I only pass parameters such as view title, number of buttons, etc ... so that then the user is deployed in the following way ...
to do so, discard certain implementations:
1.- Use a , since this generates a dead space if the list comes with few elements
2.- Use the control since this does not exist for Android-iOS
When using MVVM as an architectural pattern, it is difficult for me to pass parameters to the CodeBehind, for the previous use a MessagingCenter in which I pass a string list which I finally go through and I am adding buttons, but this is not working
you have the following view with a corresponding x: Name...
View.xaml:
<Label
Text="{Binding Titulo}"
HorizontalOptions="CenterAndExpand">
</Label>
<StackLayout
x:Name="EntriesStackLayout">
</StackLayout>
is in the ViewModel of the View where I pass the list through MessagingCenter
ViewModel.cs:
#region Constructor
public OptionsPopUpViewModel(string title, List<string> opciones)
{
Titulo = title;
opcionesMensaje = opciones;
MessagingCenter.Send((App)Xamarin.Forms.Application.Current, "Opciones", opcionesMensaje);
}
#endregion
Then in the Code behind of the View I receive in the method OnAppearing () and I create the button in the View
View.xaml.cs:
public OptionsPopUpView()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
MessagingCenter.Subscribe<App, List<string>>((App)Xamarin.Forms.Application.Current, "Opciones", (s, opcionesMensaje) =>
{
foreach (var item in opcionesMensaje)
{
Button boton = new Button();
EntriesStackLayout.Children.Add(boton);
}
});
}
protected override void OnDisappearing()
{
base.OnDisappearing();
MessagingCenter.Unsubscribe<App, List<string>>(this, "Opciones");
}
Expected behavior: Adding buttons depending on the number of items in the list
Behavior obtained: when tracking the code, the first one does not execute the OnAppering () method when I run for the second time it enters the method and it executes correctly, but it is not adding the buttons
Why am I getting this behavior?
because my code is not able to add the buttons if I am receiving the list in the MessagingCenter?
What am I doing wrong?
any help for me?
I hope this question helps those who are with the same problem
regards!

How to get text from entry

I create an entry using
<Entry Placeholder="Reply..."/>
It is inside a ListView > ItemTemplate > DataTemplate > ViewCell
The thing is I need a way once a user clicks the submit button in that ViewCell it gets the text for the entry in that cell. I am using Binding to set the values so I don't how to get the text.
When you handle the button's click event, assuming you are using an event handler to listen to the Clicked event, you can get the BindingContext of the button (which should also be the same BindingContext for the entire ViewCell).
Like so:
public void OnButtonClicked(object sender, EventArgs e)
{
// Assuming the List bound to the ListView contains "MyObject" objects,
// for example List<MyObject>:
var myObjectBoundToViewCell = (MyObject)((Button)sender).BindingContext;
// and now use myObjectBoundToViewCell to get the text that is bound from the Entry
}
Seeing your code I could notice why the #sme's answer doesn't fit you. You're making a very confusing and poor use of bindings and xaml, and I'm quite sure that move to MVVM is the best thing you can do now.
But, if you insist to keep the code like it is now, you can just add the Reply text bound to the Entry's text property, like that:
<Entry Placeholder="Reply..."
HorizontalOptions="FillAndExpand"
Margin="0, 0, 0, 5"
Text="{Binding Reply}"/>
So, as you are sending the entire MessageObjectobject to the tap command, you'll be able to get the text content just this way:
public void ReplyCommandClick(string id, object msg)
{
MessageObject message = (MessageObject) msg;
message.ShowReplyField = message.ShowReplyField ? false : true;
//var viewcell = (MessageObject)((Label)msg).BindingContext;
//viewcell. // There were no options for the entry
var reply = msg.Reply;
SendReply(id, msg, reply);
}

C# - WPF - Prevent an update of a bound focused TextBox

I have a TextBox in a Windows Desktop WPF application bound to a property of a ViewModel. Now the user focuses the TextBox and starts entering a new value. During this time a background process gets a new Value for the same Property (e.g. because another user in a multi user environment enters a new value and an observer is detecting and propagating this change) and calls a PropertyChanged event for this Property. Now the value changes and the stuff the current user just entered is lost.
Is there a built in way to prevent the change while the TextBox is focused? Or do I have to build my own solution?
I think a custom control is needed to achieve the behavior you describe. By overriding a couple methods on the default WPF TextBox, we can keep the user input even if the View Model changes.
The OnTextChanged method will be called regardless of how our textbox is updated (both for keyboard events and View Model changes), but overriding the OnPreviewKeyDown method will separate out direct user-input. However, OnPreviewKeyDown does not provide easy access to the textbox value because it is also called for non-printable control characters (arrow keys, backspace, etc.)
Below, I made a WPF control that inherits from TextBox and overrides the OnPreviewKeyDown method to capture the exact time of the last user key-press. OnTextChanged checks the time and updates the text only if both events happen in quick succession.
If the last keyboard event was more than a few milliseconds ago, then the update probably did not happen from our user.
public class StickyTextBox : TextBox
{
private string _lastText = string.Empty;
private long _ticksAtLastKeyDown;
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
_ticksAtLastKeyDown = DateTime.Now.Ticks;
base.OnPreviewKeyDown(e);
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
if (!IsInitialized)
_lastText = Text;
if (IsFocused)
{
var elapsed = new TimeSpan(DateTime.Now.Ticks - _ticksAtLastKeyDown);
// If the time between the last keydown event and our text change is
// very short, we can be fairly certain than text change was caused
// by the user. We update the _lastText to store their new user input
if (elapsed.TotalMilliseconds <= 5) {
_lastText = Text;
}
else {
// if our last keydown event was more than a few seconds ago,
// it was probably an external change
Text = _lastText;
e.Handled = true;
}
}
base.OnTextChanged(e);
}
}
Here's a sample View Model which I used for testing. It updates its own property 5 times from a separate thread every 10 seconds to simulate a background update from another user.
class ViewModelMain : ViewModelBase, INotifyPropertyChanged
{
private delegate void UpdateText(ViewModelMain vm);
private string _textProperty;
public string TextProperty
{
get { return _textProperty; }
set
{
if (_textProperty != value)
{
_textProperty = value;
RaisePropertyChanged("TextProperty");
}
}
}
public ViewModelMain()
{
TextProperty = "Type here";
for (int i = 1; i <= 5; i++)
{
var sleep = 10000 * i;
var copy = i;
var updateTextDelegate = new UpdateText(vm =>
vm.TextProperty = string.Format("New Value #{0}", copy));
new System.Threading.Thread(() =>
{
System.Threading.Thread.Sleep(sleep);
updateTextDelegate.Invoke(this);
}).Start();
}
}
}
This XAML creates our custom StickyTextBox and a regular TextBox bound to the same property to demonstrate the difference in behavior:
<StackPanel>
<TextBox Text="{Binding TextProperty, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
<TextBlock FontWeight="Bold" Margin="5" Text="The 'sticky' text box">
<local:StickyTextBox Text="{Binding TextProperty}" MinWidth="200" />
</TextBlock>
</StackPanel>

creating textbox when button click in WPF?

i already searched few stackoverflow threads related thisbut i have problem in code plz review my code and tell me where is the problem i am stuck and do not where is the problem i am new here please help me out
public partial class MainWindow : Window
{
Grid MainGrid = new Grid();
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
TextBox dynamicTextBox = new TextBox();
dynamicTextBox.Text = "Type Partnumber";
Grid.SetRow(dynamicTextBox, 1);
Grid.SetColumn(dynamicTextBox, 0);
this.MainGrid.Children.Add(dynamicTextBox);
}
}
By using the button_Click event you are adding the dynamic TextBox to the MainGrid and that code looks fine. But the problem is that this.MainGrid is not in the Present UI, its similar to the dynamically created TextBox, since you ware defined it in the code-behind(see the definition above the constructor Grid MainGrid = new Grid();).
Consider that canContainer is a canvas defined in the xaml as like the following,
<Canvas Height="319" Margin="0"
Width="517"
Name="canContainer"/>
To overcome this you can choose any of the following method.
1.Add The MainGrid to the UI. it will add the grid to the canvas, keep in mind the dynamic textBox already added to the Grid.
which means the code should be like this -->
this.canContainer.Children.Add(MainGrid);
2.Add dynamicTextBox to any other parent which is present in the UI.
this.canContainer.Children.Add(dynamicTextBox);
You you are using this method then need not to define and adding MainGrid

One property for multiple buttons

Is there a way to write in c# one property for multiple items. e.g. i have 5 buttons, i don't want to write button1.text = "etc", button2.text = "etc, I want to write button.text="etc" and have button1.text through button5.text to have "etc" text.
I guess this is feasible with something similar to:
public void SetButtonText(string value) {
this.Controls.OfType<Button>().ToList().ForEach(b => b.Text = value);
}
Or the same through a property:
public string ButtonText {
set {
Controls
.OfType<Button>()
.ToList()
.ForEach(b => b.Text = value);
}
}
EDIT
After a further research, I found out that there are no direct way to access the controls of a page in Windows Phone as I know. So it all depends on whether you wish to get down from the PhoneApplicationPage:
As I see it, your solution revolves around the Page.LogicalChildren Property.
public class MyPage : Page {
public string ButtonText {
set {
LogicalChildren
.OfType<Button>()
.ToList()
.ForEach(b => b.Text = value);
}
}
}
Since the LogicalChildren has a protected accessor, you need to access it through a derived class, which shall be convenient for any kind of page you're working on Windows Phone, I guess.
Or drop a Grid right onto the PhoneApplicationPage and then drop other controls over it such as your buttons, then you shall access them through the Grid.Children property.
So, having dropped your Grid and naming it myBaseGrid, one would do the following:
public void SetButtonsText(string text) {
myBaseGrid.Children
.OfType<Button>()
.ToList()
.ForEach(b => b.Text = "myText");
}
I would personally go with the method which name makes it clear what you're doing by spelling the word Button in plural as in my sample.
Perhaps you are looking for control arrays: http://msdn.microsoft.com/en-us/library/aa289500(v=vs.71).aspx?
You can't assign all 5 buttons to the same reference, so that button.text = "etc" will work.
You can however, bind the buttons to the same property:
<Button Content="{Binding myText}"/>
<Button Content="{Binding myText}"/>
<Button Content="{Binding myText}"/>
<Button Content="{Binding myText}"/>
If the binding is set properly with INotifyPropertyChanged, then all will update when myText is updated.
You could also put the controls into a collection and foreach over them to set their Content property as others have suggested.
One way would be to create a method that sets them all for you, which you would have to manually write once:
public void SetAllButtonTexts(string text)
{
button1.text = text;
button2.text = text;
// . . .
}
Alternatively you could use a loop:
public void SetAllButtonTexts(string btnText)
{
foreach (var control in this.Controls.OfType<Button>())
{
(control).Text = btnText;
}
}
And if you don't want to update ALL the buttons, one easy but not-so-elegant thing you could do is modify the Tag property of the buttons you want to change with some custom text, and only update those:
public void SetAllButtonTexts(string btnText, string tagText = "")
{
foreach (var control in this.Controls.OfType<Button>()
.Where(b => string.IsNullOrEmpty(tagText)
|| (b.Tag != null && b.Tag.Equals(tagText))))
{
(control).Text = btnText;
}
}
In a few words: Group up all your buttons which should get changed in a list. Then later loop through this list and set your text of all buttons.
Here's some code.
First of all:
public static List<Button> buttonList = new List<Button>{};
On form_load:
buttonList.AddRange(new List<Button>{ button1,button2,button3,...}); // Group your buttons
Now it depends on 'when' or 'where' you want to change it. If the buttons should be changed right in the beginning, put the following code into the form_load-event. Else when it should be fired on an event, place it into an event.
foreach(Button btn in buttonList)
{
btn.Text = "Change all button-texts from list at one time.";
}
You can also handle multiple lables or boxes etc. like this. Just declare the right datatype.
Greetings

Categories