I have a slider on a page, and I am trying to implement a step so that the slider only allows integer values.
In order to achieve that, I am trying to associate an event handler to the ValueChanged event. Here is my XAML code:
<Slider ValueChanged="OnSliderValueChanged" Maximum="5" Minimum="1" Value="{Binding MaxProductsToOffer}"/>
And my event handler:
public void OnSliderValueChanged(object sender, EventArgs e)
I get the following compilation error:
Position 15:21. EventHandler "OnSliderValueChanged" not found in type
"MyApp.Views.SettingsPage"
What am I missing?
A slider in Xamarin Forms has explicit ValueChangedEventArgs so your event handler should look like this
public void OnSliderValueChanged(object sender, ValueChangedEventArgs e)
https://learn.microsoft.com/en-us/dotnet/api/xamarin.forms.slider.valuechanged?view=xamarin-forms
Add the method:
void OnSliderValueChanged(object sender, EventArgs e)
{
...
}
Add it in code behind.
You do not need your event handled.
Position 15:21. EventHandler "OnSliderValueChanged" not found in type
"MyApp.Views.SettingsPage"
Means that you don't have an EvenHandler with the name "OnSliderValueChanged" on MyApp.Views.SettingsPage. Where it should be since you pointing to it in XAML -> ValueChanged="OnSliderValueChanged".
So either define it in your page or remove it from XAML.
Regarding your problem, since you are using bindings Value="{Binding MaxProductsToOffer}" I assume that you are following the MVVM pattern in your project and that you have a dedicated ViewModel bound to MyApp.Views.SettingsPage. If so you all you have to do is to bind Slider.Value to an integer property in your ViewModel that will automatically handle the conversion for you. Here is a solution that does not require any code behind:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SliderTest"
x:Class="SliderTest.MyPage"
Padding="50">
<ContentPage.BindingContext>
<local:MainViewModel />
</ContentPage.BindingContext>
<ContentPage.Content>
<StackLayout>
<Slider x:Name="slider" Maximum="5" Minimum="1" Value="{Binding SliderValue}" />
<Label Text="{Binding Source={x:Reference Name=slider}, Path=Value}" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage.Content>
</ContentPage>
public class MainViewModel : INotifyPropertyChanged
{
int sliderValue;
public int SliderValue
{
get => sliderValue;
set
{
sliderValue = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Alternatively you could: extend Slider and use a private EventHandler to manipulate the value or use an EventHandler in your page. Code example can be found here. However I find it as an overkill compare to my original proposal.
P.S.: If you are using MVVM prefer solutions that does not involve writing code in the UI layer unless it is really necessary.
Related
I walked into an issue where I wanted to use a DataTemplateSelector for a single item. In this GitHub issue I came accross an answer that extends a contentview and allows for this to happen. I now use this with two Data Templates. The bindingcontext I pass into these works great. But as soon as I want to use another bindingcontext it just says it can't find it.
My DataTemplate:
<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:Universal_ONE.ViewModels">
<DataTemplate x:Key="AirpointSettingsDataTemplate">
<StackLayout Orientation="Horizontal"
x:Name="StackName"
Spacing="10">
<Label Text="TestAirpoint" TextColor="Black" FontSize="30" FontFamily="Roboto"/>
<Label Text="{Binding U}" TextColor="Black" FontSize="30" FontFamily="Roboto">
<Label.BindingContext>
<viewmodels:AirpointSettingsViewModel/>
</Label.BindingContext>
</Label>
</StackLayout>
</DataTemplate>
</ResourceDictionary>
The viewmodel:
public class AirpointSettingsViewModel : INotifyPropertyChanged
{
public AirpointSettingsViewModel()
{
Debug.WriteLine("ViewModel Airpoint settings");
}
private string u = "sometext";
public string U
{
get => u;
set
{
u = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(U)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
When putting breakpoints in the viewmodel it does also not hit those breakpoints.
I also tested with the viewmodel as a bindingcontext of the stacklayout, but that doesn't do anything either.
The error:
[0:] Binding: 'U' property not found on 'Universal_ONE.ViewModels.AirpointSettingsViewModel', target property: 'Xamarin.Forms.Label.Text'
I have also tested with a totally unrelated viewmodel on a random property. It still won't find it then. Thus the problem is not that it can't find the viewmodel or that it is faulty.
I have a method in my view model. How I can bind this method to textbox.gotfocus property.
My XAML part is:
<TextBox Style=
"{StaticResource TextBoxHadnigPanel}"
GotFocus="{Binding GotFocusCustomerNameMethod}"
LostFocus="{Binding LostFocusCustomerNameMethod}"
x:Name="TextBoxCustomerName"
Grid.Row="0"
Grid.Column="1"
MaxLength="16"
Margin="10" />
How to bind this LostFocus and GotFocus properties?
Anyone?
Thanks in advance
You can't bind a method in WPF.
Alternative: You can use a Behavior for a TextBox with MVVM.
You need a reference to System.Windows.Interactivity to achieve this.
public class TextBoxFocusBehavior : Behavior<TextBox>
{
#region Overrides of Behavior
protected override void OnAttached()
{
AssociatedObject.GotFocus += AssociatedObject_GotFocus;
AssociatedObject.LostFocus += AssociatedObject_LostFocus;
base.OnAttached();
}
private void AssociatedObject_LostFocus(object sender, RoutedEventArgs e)
{
//TODO Your LostFocus Method here.
}
private void AssociatedObject_GotFocus(object sender, RoutedEventArgs e)
{
//TODO Your GotFocus Method here.
}
#endregion
}
Xaml:
You need a reference in the xaml file:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behaviors="clr-namespace:YourNamespace"
<TextBox Style="{StaticResource TextBoxHadnigPanel}"
x:Name="TextBoxCustomerName"
Grid.Row="0"
Grid.Column="1"
MaxLength="16"
Margin="10">
<i:Interaction.Behaviors>
<behaviors:TextBoxFocusBehavior />
</i:Interaction.Behaviors>
</TextBox>
Is it possible to create a ListView with ViewCells that will contain two Buttons and Label, first button will be "+", second "-" and a label will be a counter that will show how much "+" button has been tapped.
Then I want to be able to get from my listview an item that is binded to this viewcell and information about how much this item has been selected.
For now I created a StackLayout filled with Views thats "mocks" a Viewcells. This solution is so bad for many items because I have to create lots of Views (it takes few seconds).
So I would like to solve the problem using a ListView but I have no idea how to achive this. Or maybe you have a better solution than using a listview?
this should be trivial. First, create a data structure to hold your data
public class MyData : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private double _count;
public double Count
{
get
{ return _count; }
set
{
_count = value;
NotifyPropertyChanged();
}
}
List<MyData> data { get; set; }
you will need to initialize it with as many rows as your want to display in your list. The create a template with a Label and Buttons that are bound to your Count property
<ListView x:Name="listView" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<Label Text="{Binding Count}" />
<Button Clicked="Increment" CommandParameter="{Binding .}" Text="+" />
<Button Clicked="Decrement" CommandParameter="{Binding .}" Text="-" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
in your code-behind
protected void Decrement(object sender, EventArgs args) {
var b = (Button)sender;
var data = (MyData)b.CommandParameter;
data.Count--;
}
protected void Increment(object sender, EventArgs args) {
var b = (Button)sender;
var data = (MyData)b.CommandParameter;
data.Count++;
}
finally, use binding or direct assignment to set the List's ItemsSourcee
listView.ItemsSource = data;
I created a "WPF Application Project" in Visual Studio 2013.
I opened the "MainWindow.xaml" file and I wrote the following XAML code:
<Window x:Class="TestProject.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">
<Window.Resources>
<DataTemplate x:Key="AlphaDataTemplate">
<Label
Name="LabelInDataTemplate"
Content="Good morning!" />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentPresenter
Name="MyContentPresenter"
ContentTemplate="{StaticResource AlphaDataTemplate}" />
<Button
Name="MyButton"
Click="MyButton_OnClick"
Content="Change the content of the Label in the DataTemplate"
Width="320"
Height="30" />
</Grid>
In this XAML file I created a "DataTemplate" which corresponds to the key "AlphaDataTemplate". The DataTmplate contains just one label with the name "LabelInDataTemplate" where I have hardcoded the "Good morning!" string in the "Content" attribute of the label.
Then I use created a "ContentPresenter" with the name "MyContentPresenter" and I pass as content the "DataTemplate" I previously created (AlphaDataTemplate).
As next step, I created a "Button" with the name "MyButton" and I have set a "Click" event called "MyButton_OnClick"
So far so good...!
The question comes now and actually in C# in the code behind file "MainWindow.xaml.cs". See the code below:
using System.Windows;
namespace TestProject
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void MyButton_OnClick(object sender, RoutedEventArgs e)
{
LabelInDataTemplate.Content = "Bye!"; // <-- Tha does not work.
}
}
}
In this C# code behind file you can see the definition of the "Click" (MyButton_OnClick) event of the Button (MyButton) which appears in XAML.
What I am trying to do in this "Click" event, is to change the value of the "Content" of the "Label" (LabelInDataTemplate) which is in the DataTemplate (AlphaDataTemplate).
Unfortunately, that does not work.
I cannot actually access the "Name" (LabelInDataTemplate) of the "Label", because it is contained in the "DataTemplate" (AlphaDataTemplate)
If anyone has any idea, how could I modify from C# the value of an element which is define in a XAML DataTemplate, please give me feedback. I would really appreciate it.
Thank you in advance.
I strongly oppose your method of changing the content of label via DataTemplate, However your requirement is possible, but very subtle.
Code
private void MyButton_OnClick(object sender, RoutedEventArgs e)
{
var alphaDataTemplate = this.Resources["AlphaDataTemplate"] as DataTemplate;
var label = alphaDataTemplate.FindName("LabelInDataTemplate", MyContentPresenter) as Label;
label.Content = "It Works";
}
Please learn MVVM and use proper DataBinding for this purpose. For sake of solving this problem:
Implement INotifyPropertyChanged interface on your Window class and Define string property like below
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public string _contentMsg;
public string ContentMsg
{
get { return _contentMsg; }
set
{
_contentMsg = value;
RaisePropertyChanged("ContentMsg");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propName)
{
if(PropertyChanged !=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
In your xaml bind the ContentPresenter and update your DataTemplate label like
<ContentPresenter
Name="MyContentPresenter"
Content = "{Binding ContentMsg}"
ContentTemplate="{StaticResource AlphaDataTemplate}" />
<DataTemplate x:Key="AlphaDataTemplate">
<Label
Name="LabelInDataTemplate"
Content="{Binding}" />
Now in click handler (I would use Commands here), set ContentMsg to whatever you want
private void MyButton_OnClick(object sender, RoutedEventArgs e)
{
ContentMsg = "Bye!";
}
I am trying to display a login window once my MainWindow loads while sticking to the MVVM pattern. So I am trying to Bind my main windows Loaded event to an event in my viewmodel.
Here is what I have tried:
MainWindowView.xaml
<Window x:Class="ScrumManagementClient.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"
DataContext="ViewModel.MainWindowViewModel"
Loaded="{Binding ShowLogInWindow}">
<Grid>
</Grid>
</Window>
MainWindowViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ScrumManagementClient.ViewModel
{
class MainWindowViewModel : ViewModelBase
{
public void ShowLogInWindow(object sender, EventArgs e)
{
int i = 0;
}
}
}
The error message I am getting is "Loaded="{Binding ShowLogInWindow}" is not valid. '{Binding ShowLogInWindow}' is not a valid event handler method name. Only instance methods on the generated or code-behind class are valid."
You're going to have to use the System.Windows.Interactivity dll.
Then add the namespace in your XAML:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Then you can do stuff like:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding MyICommandThatShouldHandleLoaded}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Please note that you will have to use an ICommand (or DelegateCommand is you use Prism, or RelayCommand if you use MVVMLight), and the DataContext of your Window must hold that ICommand.
Use Attached Behavior. That is allowed in MVVM ....
(code below may / may not compile just like that)
XAML ...
<Window x:Class="..."
...
xmlns:local="... namespace of the attached behavior class ..."
local:MyAttachedBehaviors.LoadedCommand="{Binding ShowLogInWindowCommand}">
<Grid>
</Grid>
</Window>
Code Behind...
class MainWindowViewModel : ViewModelBase
{
private ICommand _showLogInWindowCommand;
public ICommand ShowLogInWindowCommand
{
get
{
if (_showLogInWindowCommand == null)
{
_showLogInWindowCommand = new DelegateCommand(OnLoaded)
}
return _showLogInWindowCommand;
}
}
private void OnLoaded()
{
//// Put all your code here....
}
}
And the attached behavior...
public static class MyAttachedBehaviors
{
public static DependencyProperty LoadedCommandProperty
= DependencyProperty.RegisterAttached(
"LoadedCommand",
typeof(ICommand),
typeof(MyAttachedBehaviors),
new PropertyMetadata(null, OnLoadedCommandChanged));
private static void OnLoadedCommandChanged
(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
var frameworkElement = depObj as FrameworkElement;
if (frameworkElement != null && e.NewValue is ICommand)
{
frameworkElement.Loaded
+= (o, args) =>
{
(e.NewValue as ICommand).Execute(null);
};
}
}
public static ICommand GetLoadedCommand(DependencyObject depObj)
{
return (ICommand)depObj.GetValue(LoadedCommandProperty);
}
public static void SetLoadedCommand(
DependencyObject depObj,
ICommand value)
{
depObj.SetValue(LoadedCommandProperty, value);
}
}
DelegateCommand source code can be found on the internet... Its the most suited ICommand API available for MVVM.
edit:19.07.2016 two minor syntax errors fixed
Update:
I made a post about a new more flexible version of the method binding that uses a slightly different syntax here:
http://www.singulink.com/CodeIndex/post/updated-ultimate-wpf-event-method-binding
The full code listing is available here:
https://gist.github.com/mikernet/7eb18408ffbcc149f1d9b89d9483fc19
Any future updates will be posted to the blog so I suggest checking there for the latest version.
Original Answer:
.NET 4.5+ supports markup extensions on events now. I used this to create a method binding that can be used like this:
<!-- Basic usage -->
<Button Click="{data:MethodBinding OpenFromFile}" Content="Open" />
<!-- Pass in a binding as a method argument -->
<Button Click="{data:MethodBinding Save, {Binding CurrentItem}}" Content="Save" />
<!-- Another example of a binding, but this time to a property on another element -->
<ComboBox x:Name="ExistingItems" ItemsSource="{Binding ExistingItems}" />
<Button Click="{data:MethodBinding Edit, {Binding SelectedItem, ElementName=ExistingItems}}" />
<!-- Pass in a hard-coded method argument, XAML string automatically converted to the proper type -->
<ToggleButton Checked="{data:MethodBinding SetWebServiceState, True}"
Content="Web Service"
Unchecked="{data:MethodBinding SetWebServiceState, False}" />
<!-- Pass in sender, and match method signature automatically -->
<Canvas PreviewMouseDown="{data:MethodBinding SetCurrentElement, {data:EventSender}, ThrowOnMethodMissing=False}">
<controls:DesignerElementTypeA />
<controls:DesignerElementTypeB />
<controls:DesignerElementTypeC />
</Canvas>
<!-- Pass in EventArgs -->
<Canvas MouseDown="{data:MethodBinding StartDrawing, {data:EventArgs}}"
MouseMove="{data:MethodBinding AddDrawingPoint, {data:EventArgs}}"
MouseUp="{data:MethodBinding EndDrawing, {data:EventArgs}}" />
<!-- Support binding to methods further in a property path -->
<Button Content="SaveDocument" Click="{data:MethodBinding CurrentDocument.DocumentService.Save, {Binding CurrentDocument}}" />
View model method signatures:
public void OpenFromFile();
public void Save(DocumentModel model);
public void Edit(DocumentModel model);
public void SetWebServiceState(bool state);
public void SetCurrentElement(DesignerElementTypeA element);
public void SetCurrentElement(DesignerElementTypeB element);
public void SetCurrentElement(DesignerElementTypeC element);
public void StartDrawing(MouseEventArgs e);
public void AddDrawingPoint(MouseEventArgs e);
public void EndDrawing(MouseEventArgs e);
public class Document
{
// Fetches the document service for handling this document
public DocumentService DocumentService { get; }
}
public class DocumentService
{
public void Save(Document document);
}
More details can be found here: http://www.singulink.com/CodeIndex/post/building-the-ultimate-wpf-event-method-binding-extension
The full class code is available here:
https://gist.github.com/mikernet/4336eaa8ad71cb0f2e35d65ac8e8e161
A more generic way using behaviors is proposed at AttachedCommandBehavior V2 aka ACB and it even supports multiple event-to-command bindings,
Here is a very basic example of use:
<Window x:Class="Example.YourWindow"
xmlns:local="clr-namespace:AttachedCommandBehavior;assembly=AttachedCommandBehavior"
local:CommandBehavior.Event="Loaded"
local:CommandBehavior.Command="{Binding DoSomethingWhenWindowIsLoaded}"
local:CommandBehavior.CommandParameter="Some information"
/>
For VS 2013 Update 5 I wasn't able to get around "Unable to cast object of type 'System.Reflection.RuntimeEventInfo' to type 'System.Reflection.MethodInfo". Instead in a "Core" directory I made a simple interface
interface ILoad
{
void load();
}
My viewModel already had the load() function implementing ILoad. In my .xaml.cs I call the ViewModel load() through ILoad.
private void ml_Loaded(object sender, RoutedEventArgs e)
{
(this.ml.DataContext as Core.ILoad).load();
}
The xaml.cs knows nothing about the ViewModel except the POCO ILoad, the ViewModel knows nothing about the xaml.cs. The ml_loaded event is mapped to ViewModel load().