I'd like to know how I can handle a KeyDown Event with MVVM in my ViewModel.
I have a TextBox and when the user hits a key, which is not a number, the input shouldn't be allowed. I'd normally do it with Code behind like this (not full code, just an easy example):
private void textBox1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
{
// Determine whether the keystroke is a number from the top of the keyboard.
if (e.KeyCode < Keys.D0 || e.KeyCode > Keys.D9)
{
e.Handled = true;
}
}
Now I want to put this somehow in my ViewModel with a Command. I'm new to MVVM and I'm only using Bindings right now (which works fine :) ), but I don't know how to use a Command at all...
My TextBox looks i.e. like this:
<TextBox Text="{Binding MyField, Mode=TwoWay}"/>
ViewModel:
private string _myfield;
public string MyField{
get { return _myfield; }
set {
_myfield= value;
RaisePropertyChanged( ()=>MyField)
}
}
But the setter will only be called, when I leave the TextBox + I don't have access to the entered Key.
I know my answer is late but if someone has a similar problem. You must just set your textbox like this:
<TextBox Text="{Binding MyField, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
The following works for handling the "Enter" key in a TextBox:
<TextBox Text="{Binding UploadNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding
Key="Enter"
Command="{Binding FindUploadCommand}" />
</TextBox.InputBindings>
</TextBox>
I do this by using the interaction triggers. (this example uses the MVVM_Light framework for the command binding)
here is an example:
<textBox Text="{Binding MyField}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="KeyDown">
<cmd:EventToCommand Command="{Binding MyCommandName}" CommandParameter="YouCommandParameter"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBox/>
Create a ICommand object in your view model with the name MyCommandName and add these to the top of your xaml:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cmd="http://www.galasoft.ch/mvvmlight"
You don't have to use the mvvm-light command. This is just what I use and I like it because it allows me to use the CanExecute method of the ICommand interface
hope this helps
Related
I have Xamarin Forms solution and on one page there is list with images. When image is clicked, I would like to trigger procedure witch has image path as parameter. In xaml I define image with:
<Image Source="{Binding ImagePath}"
WidthRequest="30" HeightRequest="30"
HorizontalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand" >
<Image.GestureRecognizers>
<TapGestureRecognizer Command="{Binding TapImageCommand}" CommandParameter="{Binding ImagePath}" />
</Image.GestureRecognizers>
</Image>
and TapImageCommand is defined in view model's constructor as:
TapImageCommand = new Command<string>(ImagePath =>
{
OnImageTapped(ImagePath);
});
and TapImageCommand is defined with:
public ICommand TapImageCommand { get; set; }
Problem is OnImageTapped is never triggered. What am I doing wrong?
The problem here is that you think you are binding the ImagePath of the object behind the list, but you are not. Look at the Command you are binding, this is part of the PageModel, not the object in the list, so neither is the CommandParameter.
Therefore, ImagePath is probably null, and it does not match the signature you have for the Command which expects a string.
In this particular case it is probably easiest to supply the whole object as a parameter and get out the property yourself. I will assume the object in the list is of type Foo, then edit your code like underneath.
In your view, edit the TapGestureRecognizer to this:
TapGestureRecognizer Command="{Binding TapImageCommand}" CommandParameter="{Binding .}" />
The dot points to itself, in this case the specific instance of Foo in your list. Then edit the Command like this:
TapImageCommand = new Command<Foo>(fooObject =>
{
OnImageTapped(fooObject);
});
Now in your OnImageTapped method you can extract the ImagePath property.
In my case I was looking for selected Index of stack layout or grid and it was done as below.
Command Added as below
public ICommand SelectedItem
{
get
{
return new Command<string>((x) => OpenChild(x));
}
}
public void OpenChild(string x)
{
//handle parameter x to say "Hello " + x
}
Added command on XAML
SelectionChangedCommand="{Binding SelectedItem}" SelectionChangedCommandParameter="{Binding SelectedItem, Source={RelativeSource Self}}
I have a books list displayed in my long list selector like this
<DataTemplate x:Key="BooksItemTemplate">
<StackPanel Grid.Column="1" Grid.Row="0" VerticalAlignment="Top">
<TextBlock Name="booktitle" Text="{Binding BookTitle,Mode=OneWay}" Style="{StaticResource PhoneTextNormalStyle}" TextWrapping="Wrap" FontFamily="{StaticResource PhoneFontFamilySemiBold}"/>
<TextBlock Text="{Binding AuthorName,Mode=OneWay}" Style="{StaticResource PhoneTextNormalStyle}" TextWrapping="Wrap" FontFamily="{StaticResource PhoneFontFamilySemiLight}"/>
<Button Content="Add To Favourites" Tag="{Binding BookId,Mode=OneWay}" Click="Button_Click_1" ></Button>
</StackPanel>
</Grid>
</DataTemplate>
<phone:LongListSelector x:Name="bookslist" Grid.Row="1"
ListFooter="{Binding}"
ItemsSource="{Binding BooksList}"
Background="Transparent"
IsGroupingEnabled="False"
ListFooterTemplate ="{StaticResource booksListFooter}"
ItemTemplate="{StaticResource BooksItemTemplate}"/>
so there is an add to favourites button beside every book in the list . pressing that button i am entering the pressed book's id in my isolatedstoragesetting like this
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Button bt = (Button)sender;
IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
List<long> ListFavourites;
if (settings.Contains("ListFavourites"))
{
ListFavourites = settings["ListFavourites"] as List<long>;
}
else
{
ListFavourites = new List<long>();
}
if(!ListFavourites.Contains(Convert.ToInt64(bt.Tag)))
{
ListFavourites.Add(Convert.ToInt64(bt.Tag));
}
settings["ListFavourites"] = ListFavourites;
settings.Save();
}
problem:
now when loading the above book list(longlistselector) when the page loads i want to show or hide the add to favorites button based on whether the bookid is present in the isolatedstoragesetting or not. what i have tried here is that i have tried to bind the converter to the add to favourite button and also bind the convertparameter with bookid. but the convertparameter doesn't support binding.
so what is the technique i can use to show or hide the add to favourite button based on the book id presence in the favourite list in the isolatedstoragesetting?
how can i hide the button based when clicking it based on the bookid?
You are almost there in thinking to use a converter. The actual idea, when materialized, should look something like this.
First, you will need to implement a converter, in this case, you will need to convert the bookid to a Visibility value.
public class BookIdToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
//value is booking id here, which means that you just need to check against the isolatedStorageSettings
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return null;
}
}
In your DataTemplate, the binding should take place like this:
<Button Content="Add To Favourites" Tag="{Binding BookId,Mode=OneWay}"
Click="Button_Click_1" Visibility={Binding BookId,Converter={StaticResource TheConverterCreatedAbove}} >
</Button>
That should do the trick.
The MVVM way would be to expand your ViewModel. It would be much better to add an AddToFavoritesCommand to BookViewModel instead of putting the logic in code behind. If the button is bound to that Command it would automatically go disabled when the Command would properly (with CanExecuteChanged) switch CanExecute to false.
In your case, you can add a property IsFavourite or CanAddToFavoirtes and then use a standard BoolToVisibility converter (the Command execution would set this property and the BookViewModel would be initialized with the correct value read from IsolatedStorage).
All logic behind the presentation of Book and functionalities related to Book belong to BookViewModel.
I'm using MVVM in this project, I have a listbox which bind to a collection of Customers. I want to create an event to navigate a detailsPage using id of the elementselected:
<ListBox ItemsSource="{Binding Customers}" x:Name="state_list" SelectionChanged="state_list_SelectionChanged">
<i:Interaction.Triggers>
<i:EventTrigger EventName="selectionchanged">
<cmd:EventToCommand Command="{Binding stateSelectedCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding nom}" />
<!--TextBlock Text="{Binding LastName}" />
<TextBlock Text="{Binding Text, ElementName=tbCount}" /-->
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I can't figure out how to get the selected item to add it to the uri and then use it to get data. An example or tutorial would be helpful. Thanks :)
I would create a "SelectedCustomer" property in the ViewModel (next to you Customers property) and bind it to the SelectedItem. Then, on the setter of that property you can navigate to your desired page. This way you eliminate the messy events and command.
<ListBox x:Name="state_list
ItemsSource="{Binding Customers}"
SelectedItem="{Binding SelectedCustomer, Mode=TwoWay}">
...
public Customer SelectedCustomer
{
get
{
return _selectedCustomer;
}
set
{
if (value != null)
{
_selectedCustomer = value;
//Navigate to your page here, either with Navigator class or any other mechanism you have in place for changing content on screen
}
}
}
AlexDrenea gives you a good way of binding SelectedItem to a property on your viewmodel. If you are wanting to navigate based on this in an MVVM architecture, I would suggest using messaging to get it done.
I cover this in a blog post I did a while back, but the short summary of doing this within MVVMLight, is to create a Navigator class that sits at the application level.
public class Navigator
{
private PhoneApplicatoinFrame RootFrame;
public Navigator(PhoneApplicationFrame frame)
{
RootFrame = frame;
RegisterMessages();
}
private void RegisterMessages()
{
Messenger.Default.Register<ShowTrackerMessage>(this, ShowTracker);
}
private void ShowTracker(ShowTrackerMessage msg)
{
RootFrame.Navigate(new Uri("/Views/ItemLocationCompassView.xaml", UriKind.RelativeOrAbsolute));
}
}
Then, as part of your application start-up, create it and pass it a reference to your RootFrame:
private static Navigator _navigator;
public static Navigator Nav
{
get { return _navigator; }
}
...
_navigator = new Navigator(this.RootFrame);
Then, you have a couple choices on how you send the Navigation message.
Option 1: In your ViewModel, hook into the PropertyChanged event (part of INotifyPropertyChanged), and send the appropriate message when your SelectedItem property changes.
Option 2: Tie into the SelectionChanged event of your ListBox. I use the MVVMLight's EventToCommand to send that event to a RelayCommand in my ViewModel, then react appropriately to send the message to the Navigator object.
I cover this in more detail at: http://www.smartchitecture.com/?p=27
This has to be simple, at least it was in good old .Net where it took maybe four lines of code. I'm working in VS2010, C#, WPF4.
I have a user control with a textbox. When I click a button in the main window, I want my user control textbox to reflect some text. Is this possible in WPF4 with less than 500 lines of esoteric code?
The problem is that while I know the textbox is getting the new text as evidenced from breakpoints in the user control code, that text is never being reflected to the main window. The main window still shows the original text. It has to be some kind of binding thing, and I really don't think I should have to create templates and resources and all for this simple situation. It's got to be something simple that I'm forgetting in the forest of WPF4. Below is what I have. After clicking the button, the textbox is still blank; it does not say "hello earthlings."
In the user control code:
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty TextProperty;
public UserControl1()
{
InitializeComponent();
}
static UserControl1()
{
TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(UserControl1), new UIPropertyMetadata(null));
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
User control xaml:
<UserControl x:Class="WTFUserControlLibrary.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Grid Height="164" Width="220">
<TextBox Name="txtTest" BorderBrush="red" BorderThickness="2" Height="25" Text="{Binding ElementName=UserControl1, Path=Text, Mode=TwoWay}"></TextBox>
</Grid>
(I have no idea what the text binding is supposed to be doing in this case.)
Main window code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
WTFUserControlLibrary.UserControl1 uc = new WTFUserControlLibrary.UserControl1();
uc.Text = "hello earthlings";
}
}
and the main window xaml:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:WTFUserControlLibrary;assembly=WTFUserControlLibrary"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="71,65,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
<my:UserControl1 HorizontalAlignment="Left" Margin="71,94,0,0" Name="userControl11" VerticalAlignment="Top" Height="116" Width="244" />
</Grid>
Thanks Earthlings (and also those who designed this mess!)
In your method button1_Click you are creating a new user control. This is not the usercontrol in the window and is never displayed.
Instead, give your usercontrol a name in the XAML:
x:Name="uc"
Then in the button1_Click method you just remove that first line where you create a new usercontrol.
update
You want the user control XAML to look more like this:
<UserControl x:Class="WTFUserControlLibrary.UserControl1"
x:Name="thisControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Grid Height="164" Width="220">
<TextBox Name="txtTest" BorderBrush="red"
BorderThickness="2" Height="25"
Text="{Binding ElementName=thisControl, Path=Text, Mode=TwoWay}" />
</Grid>
I added x:Name="thisControl" to the root UserControl element, and then referenced this in the binding.
I'll try to explain the binding:
You have this textbox inside your user control, but you want to be able to bind the text value to something outside the user control. What you've done is set up a dependency property on the user control, and bound that textbox to it, so you are using the binding infrastructure pass values from the top level of the UserControl to constituent controls inside it.
Basically, it looks like this:
data
---> bind UserControl1.Text to data
---> bind TextBox.Text to UserControl1.Text
I'm trying the MVVM Light Toolkit. Though I still think having multiple ViewModels for such small apps is overkill, I like the concepts. What I still can't quite understand is how (or I should say "what is the recommended way") to navigate from one page to another when the selection changes in a ListBox.
The big problem with this toolkit is that it forces you to learn MVVM via other sources before using it, rather than show you what (its vision of) MVVM is from within the framework, accompanying samples and documentation. Are there samples out there showing the different concepts? And please, no videos.
Have you tried modifying your ListBox ItemTemplate to have each item be a HyperlinkButton and just setting the NavigateURI attribute to the Page you want to navigate to?
I still have not figured out how to do this (navigate to a details page upon selection changed in a listbox) without any codebehind in the view. However, if you are OK with having just a little codebehind in the view here's what I recommend:
<ListBox x:Name="MainListBox" Margin="0,0,-12,0" ItemsSource="{Binding Items}"
SelectionChanged="MainListBox_SelectionChanged"
SelectedItem="{Binding Path=SelectedListItem, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432">
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
First, per above bind to the SelectedItem property of the Listbox with a TwoWay binding to a property in your ViewModel (SelectedListItem in the above).
Then in your codebehind for this page implement the handler for MainListBox_SelectionChanged:
// Handle selection changed on ListBox
private void MainListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// If selected index is -1 (no selection) do nothing
if (MainListBox.SelectedIndex == -1)
return;
// Navigate to the new page
NavigationService.Navigate(new Uri("/DetailsPage.xaml", UriKind.Relative));
}
This is the only codebehind you need in your main view.
In your main ViewModel you need a SelectedListItem property:
public const string SelectedListItemPropertyName = "SelectedListItem";
private ItemViewModel _SelectedListItem;
/// <summary>
/// Sample ViewModel property; this property is used in the view to display its value using a Binding
/// </summary>
/// <returns></returns>
public ItemViewModel SelectedListItem
{
get
{
return _SelectedListItem;
}
set
{
if (value != _SelectedListItem)
{
_SelectedListItem = value;
RaisePropertyChanged(SelectedListItemPropertyName);
}
}
}
Now, the trick to getting the context passed to your details page (the context being what list item was selected) you need to setup the DataContext in your Details view:
public DetailsPage()
{
InitializeComponent();
if (DataContext == null)
DataContext = App.ViewModel.SelectedListItem;
}
Hope this helps.
eventually you'll want to do more than just navigate, potentially navigate after setting a custom object.
Here is a MVVM-light way of doing this.
You'll first want to bind your listbox selected item to a property in your viewmodel
<ListBox ItemsSource="{Binding Events}" Margin="0,0,-12,0" SelectedItem="{Binding SelectedEvent, Mode=TwoWay}">
Declare your SelectedEvent property
public const string SelectedEventPropertyName = "SelectedEvent";
private Event _selectedEvent;
public Event SelectedEvent
{
get {return _selectedEvent;}
set
{
if (_selectedEvent == value)
{
return;
}
var oldValue = _selectedEvent;
_selectedEvent = value;
// Update bindings and broadcast change using GalaSoft.MvvmLight.Messenging
RaisePropertyChanged(SelectedEventPropertyName, oldValue, value, true);
}
}
You can then define an interaction trigger bound to the tap event
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<cmd:EventToCommand Command="{Binding EventPageCommand, Mode=OneWay}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
In your viewmodel, define your EventPageCommand as a RelayCommand:
public RelayCommand EventPageCommand { get; private set; }
public MainViewModel()
{
EventPageCommand = new RelayCommand(GoToEventPage);
}
and finally declare your GoToEventPage method
private void GoToEventPage()
{
_navigationService.NavigateTo(new Uri("/EventPage.xaml", UriKind.Relative));
}
note that you can do other actions before navigating to your new page, plus your selected item from your list box is currently set in the property you bound it too.