I want to be able to click ListView item, which then takes me to appropriate page. But since there doesn't exists anything like ClickedItem to go along with the ItemClick, I have to use the SelectedItem (to get the object of what the user clicked) and SelectionChanged to capture when it happens (because this is setup in a way that when user clicks, he makes a selection, which triggers this).
Since in MVVM I can't use events, I'm binding what would be events to methods in my ViewModel.
<GridView x:Name="MyGrid"
ItemsSource="{x:Bind ViewModel.myList, Mode=OneWay}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
IsSwipeEnabled="false"
SelectedItem="{Binding mySelectedItem, Mode=TwoWay}" // Binding makes it easier to bind the whole object
SelectionChanged="{x:Bind ViewModel.SelectioMade}"
>
I fill up my list in the ViewModel. I'm using Template10 implementation of INotifyPropertyChanged.
private MyListItemClass _mySelectedItem;
public MyListItemClass mySelectedItem{
get { return _mySelectedItem; }
set { Set(ref _mySelectedItem, value); }
}
And this simple method pushes me to the next page when user clickes on an item.
public void SelectioMade() {
if (_mySelectedItem != null) {
NavigationService.Navigate(typeof(Views.DetailPage), _mySelectedItem.id);
}
}
This works.
Problem is that a selection is made and it persists. When I hit the back button on the DetailPage, I go back to this list as I left it and the clicked item is still selected. And hence, clicking it again doesn't actually make a selection and trigger the SelectionChanged.
Obvious choice seemed to be to just set mySelectedItem to null when I no longer need the value, but it doesn't work.
public void SelectioMade() {
if (_mySelectedItem != null) {
NavigationService.Navigate(typeof(Views.DetailPage), _mySelectedItem.id);
mySelectedItem = null;
}
}
I can't seem to be able to set it back to null. If I place a break point on the mySelectedItem = null; it just doesn't do anything. It does trigger the set { Set(ref _mySelectedItem, value); }, but the View doesn't update. Neither the clicked item becomes deselected, nor a TextBlock I bound to one of the mySelectedItem.id properties gets changed (or rather emptied).
I would like to know why doesn't this work and possibly how to fix it. My MVVM may not be perfect, I'm still learning. And while it may not be perfect, I'm not really looking for advice how to properly write MVVM. I want to know why this doesn't work, because in my opinion, it should work just fine.
It seems that GridView doesn't like the SelectedItem property being changed within the SelectionChanged handler (it could result in an infinite loop if guards are not used). You could instead set SelectedItem to null in the OnNavigatedTo handler for that page (or whatever the Template 10 equivalent of that is).
Also you don't really need to subscribe to the SelectionChanged event since you can detect this in the setter of your mySelectedItem property.
However, I think it is wrong to handle item clicks by listening for selection changed events because the selection can be changed by other means (up/down arrow key, or tab key, for example). All you want to do is to respond to an item click and obtain the clicked item, right? For this, you can x:Bind the ItemClick event to a method in your view model:
<GridView ItemClick="{x:Bind ViewModel.ItemClick}" SelectionMode="None" IsItemClickEnabled="True">
public void ItemClick(object sender, ItemClickEventArgs e)
{
var item = e.ClickedItem;
}
If you're uneasy about the ItemClick method signature in your view model, then you can make your own ItemClick behavior to execute a Command exposed in your view model with the command's parameter bound to the clicked item.
If you're not using behaviors for some reason, then you can make your own attached property instead, something like this:
public class ViewHelpers
{
#region ItemClickCommand
public static readonly DependencyProperty ItemClickCommandProperty =
DependencyProperty.RegisterAttached("ItemClickCommand", typeof(ICommand), typeof(ViewHelpers), new PropertyMetadata(null, onItemClickCommandPropertyChanged));
public static void SetItemClickCommand(DependencyObject d, ICommand value)
{
d.SetValue(ItemClickCommandProperty, value);
}
public static ICommand GetItemClickCommand(DependencyObject d)
{
return (ICommand)d.GetValue(ItemClickCommandProperty);
}
static void onItemClickCommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var listView = d as ListViewBase;
if (listView == null)
throw new Exception("Dependency object must be a ListViewBase");
listView.ItemClick -= onItemClick;
listView.ItemClick += onItemClick;
}
static void onItemClick(object sender, ItemClickEventArgs e)
{
var listView = sender as ListViewBase;
var command = GetItemClickCommand(listView);
if (command != null && command.CanExecute(e.ClickedItem))
command.Execute(e.ClickedItem);
}
#endregion
}
XAML doesn't require MVVM patterns to be used, which means there is lots of "missing" functionality that you need to write yourself to make MVVM easier for you (like the above ItemClick attached property). Maybe Template 10 provides some behaviors for you already? I'm not familiar with it.
My first instinct would be to check your Set method, to ensure that it is really sending the proper notification to the view. I am not familiar with the Template10 implementation, so it seems strange to me that you are not required to provide a property name with Set().
Beyond that, I would suggest that you go back to using Click rather than SelectionChanged, since that is the behavior you are actually interested in. You should read a bit about attached properties, which are a great way to accomplish tasks that would normally require code-behind without actually using code-behind. They make MVVM a lot more practical and a lot less hackish.
https://msdn.microsoft.com/en-us/library/ms749011(v=vs.110).aspx
An attached property, more or less, allows you to define a DependencyProperty that you can attach to any element in XAML, like your GridView. Because you get access to the Element in the setter, you are free to attach to its events. So, you can create an attached property with a delegate type, which will forward an event like click to the delegate. Back in the view, you bind it to your handler in the ViewModel like this:
<GridView something:MyAttachedProperties.ClickHandler="{Binding MyClickHandler}" />
Hope this helps!
SelectedIndex = -1, following your null set of the SelectedItem property? So yes another property would be required or make sure that caching is disabled for that page as well.
Related
In my View I have 8 checkboxes all bound to a different property in the Model, lets say bool port1 ... port8. The property changes when the concerned checkbox is clicked.
When the user checks or unchecks one of these checkboxes, I also execute a binded command 'SetPortCommand'. The command executes a function like SetPort(uint numPort, bool set)
My view looks like this:
<CheckBox x:Name="cbPort1" Content="port1" Command="{Binding SetPortCommand}">
<CheckBox.IsChecked>
<Binding Path="MyModel.Port1"/>
</CheckBox.IsChecked>
</CheckBox>
Behind the whole thing is quite slow hardware, so I would like to avoid calling the function SetPort() for each port. I could use 8 commands like SetPortCommand1 to SetPortCommand8, but that causes lots of duplicate code.
I had the following ideas, but I do not know how to implement it, neither which one wouldf be conform to MVVM.
idea would be to somehow pass a constant to the command, telling it which of the 8 ports it should check or uncheck. Is there a easy way to do this?
idea would be to somehow use the OnPropertyChanged()-Event to call the function in here.
Any help appreciated :-)
You could avoid exeucting the command and just call your method from each setter, e.g.:
private bool _port1;
public bool Port1
{
get { return _port1; }
set { _port1 = value; SetPort(); }
}
If the SetPort modifies the other properties, you could use a flag to determine whether it should be invoked:
private bool _setPort = true;
private bool _port1;
public bool Port1
{
get { return _port1; }
set
{
_port1 = value;
if (_setPort)
{
_setPort = false;
SetPort();
_setPort = true;
}
}
}
You could do the same thing in the Execute method of your command if you still want to use a command for some reason.
The _setPort field prevents the method from getting called for each source property.
Alternatively, you could also avoid setting the property in the method and instead set the backing field and raise the PropertyChanged event:
_port2 = true;
OnPropertyChanged(nameof(Port2));
This will bypass the setter and avoid calling the method.
With a checkbox, the usual approach is to bind ischecked and to act in that setter. This uses text rather than a bool, but you can see there's a method called in the setter. https://social.technet.microsoft.com/wiki/contents/articles/30564.wpf-uneventful-mvvm.aspx#Change_of_Variable
You have a repeating group of functionality in this set of check boxes.
The usual approach is to bind a list or observablecollection of viewmodels to the itemssource of an itemscontrol. Template that data out into your ui. Here you'd have checkboxes. Each of your row viewmodels would hold data about a port. Name and whatnot.
If you particularly want a command you could have an ICommand such as delegatecommand or relaycommand bound from either:
The row viewmodel - in which case that "knows" which port is to be acted on. And your code that does stuff with that port is in the row viewmodel.
or
To the window viewmodel, in which case the row viewmodel is the datacontext of the button and this is passed as a parameter to the ICommand.
Both these are common scenarios working with buttons and you should be able to find code googling.
Binding IsChecked would be simpler and is hence more usual though.
I have the TreeView with objects. When I select item of these tree, than other control - ListView displays as its items properties of selected object. I want to save values of properties when in TreeView selection is change to other object.
So, is there a good way in WPF to gets values of "just before changing" items in ListView control? My idea for now is to override the PreviewMouseDown to check if user click tree node. By god way I mean better than mine. Maybe something in ListView template?
Indication that there is no need to change my idea with the PreviewMouseDown will be also good answer.
Could you please provide the relevant code snippets? I try to answer your question, but I'm not sure I understood it correctly.
If you bind the SelectedItem of you TreeView to a property (a.e. using MVVM pattern), you can save the values before actually setting the item.
Doing so in the setter is not so good though, because it becomes quite large then. I would have a setter like this:
private Foo bar;
public Foo Bar
{
get { return bar; }
set
{
OnPropertyChanging("Bar");
bar=value;
OnPropertyChanged("Bar");
}
}
Then you can listen to your own PropertyChanging events and do your stuff there:
private void this_PropertyChanging(object param, PropertyChangingEventArgs e)
{
switch(e.PropertyName)
{
case "Bar":
//Do you stuff
break,
}
}
Well, the title is not very descriptive because they do not how to name what I'm looking for. I'll try to explain it as best I can.
In the .xaml file, a control (Suppose a textbox), if you type "Text", this can not be used again property. If we write by hand, the compiler displays an error. Good already explained why I think it's now easier.
Suppose now that I have two (DependencyProperty.RegisterAttached ... ...) with the name "Propiedad_1" and "Propiedad_2" is possible, if I'm already using one, the other can not be used in the same control and vice versa?
2) Another question, within a dependency property of type string, is it possible to check if the String changed at some point (around trying to not use a variable and then comparing), I need to avoid spending a TexBox and avoid the textbox.TextChanged event.
Thanks!
EDIT
This is what I have now.
public static readonly DependencyProperty FilterSourceProperty =
DependencyProperty.RegisterAttached("FilterSource", typeof (TextBox), typeof (ListViewExtension),
new FrameworkPropertyMetadata(null, OnTextBoxTextChanged));
public static TextBox GetFilterSource(DependencyObject dObj)
{
return (TextBox) dObj.GetValue(FilterSourceProperty);
}
public static void SetFilterSource(DependencyObject dObj, TextBox value)
{
dObj.SetValue(FilterSourceProperty, value);
}
private static void OnTextBoxTextChanged(DependencyObject dObj, DependencyPropertyChangedEventArgs e)
{
var listView = dObj as ListView;
var textBox = e.NewValue as TextBox;
if ((listView == null) || (textBox == null)) return;
textBox.TextChanged += delegate(object sender, TextChangedEventArgs tcea)
{
var view = CollectionViewSource.GetDefaultView(listView.ItemsSource);
if (view == null) return;
view.Filter += item =>
{
...
...
...
...
};
};
}
In .XAML
<TextBox Name="TxtFilter"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
Text="{Binding Filter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" CharacterCasing="Upper"/>
<ListView Margin="0,5,0,0"
ItemsSource="{Binding Articles}"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding SelectedArticle}"
tools:ListViewExtension.FilterSource="{Binding ElementName=TxtFilter}">
In ViewModel
public string Filter
{
get { return _filter; }
set
{
if (_filter == value) return;
_filter = value;
RaisePropertyChanged();
}
}
What I need now is change the use of a "TextBox" by a string.
tools:ListViewExtension.FilterSource="{Binding Filter}"
Your question is fairly hard to understand, due to the lack of a good, minimal, complete code example as well as the poor English. The latter is understandable (though you must still do what you can to ensure you're communicating as well as possible), but the former definitely should be addressed.
Lacking those improvements, I will guess that your questions (both…for future reference, please do not post two different questions in the same post) amount to the following:
I have two attached properties, which I would like to make mutually exclusive in the XAML editor. Is this possible?
No. The behavior you're seeing applies only to a single property. The editor will complain if you try to set the same property more than once. But it's easy for it to do that, since all it has to do is check whether that property was already used in the element.
For two different properties that are supposed to be mutually exclusive, there's not any feasible way to modify the editor or compiler's behavior to check this.
As an alternative, consider implementing the two mutually-exclusive values as a single property, where that property can accept two different subclasses of a given type, and where those subclasses each represent one of the two mutually exclusive property types.
Can I optimize property updates, so that if a new value is assigned that is actually the same as the current value, no "property changed" event is raised?
Whether this is possible depends on how your code is actually written. In WPF, binding is supported through the use of DependencyProperty or INotifyPropertyChanged. Neither of these would imply a TextChanged event in an object for a Text property, which you stated is the event you don't want raised.
Note that in general, DependencyObject.SetValue() will suppress change notifications if the effective value (after coercion) has not actually changed. Note also that in most other cases, extra change notifications would not normally be a real performance issue.
Lacking a good code example, not much more advice on that second question can be offered.
If you feel that these answers don't reasonably or usefully address your questions, please improve your post so that it is more understandable.
EDIT:
With respect to the first question, and based on the code snippet you've provided (not complete), I would say that the simplest approach would be to make FilterSource have type object instead of TextBox, and then in OnTextBoxTextChanged(), check the type of the new value and handle appropriately. I.e. if it's a TextBox, do what you're doing now (mostly…see (*) below), and if it's a string instance, just configure the view's filter directly instead of putting the configuration into an event handler.
(*) note:
I see at least two areas of improvement in your OnTextBoxTextChanged() method:
There is no need to rebuild the Filter event handler just because the text's changed. Instead, you can just call Refresh() on the view. So in that approach, you would implement the event handler for the Filter event to always retrieve the TextBox.Text property value for filtering. You would subscribe to the event once, and then the event handler for TextChanged would just call Refresh().
In the string scenario, you'd use a Filter event handler that just filters using the string value, with no need to handle the (non-existent, of course) TextChanged event.
The bigger issue is that you only ever subscribe to the TextChanged event. If you only ever change the FilterSource property once, you'll never notice a problem, but it is a problem. If the property value is ever changed again, you should be unsubscribing the old event handler before subscribing a new one. If you make the change I describe above, where the TextChanged event handler is only calling Refresh(), the impact of this bug will be significantly reduced. But it's still a bug.
end of note.
As far as the second part of your question goes, I don't see a problem that needs solving. It's not clear whether you're concerned about the TextBox.Text property or the FilterSource property, but I think that neither property should generate change notifications if the newly set property value is the same as the old.
If you think differently, please provide a better code example (both minimal and complete) that illustrates clearly what actual problem occurs, along with a clear, precise explanation of what that problem is: how the code currently behaves, and how that's different from how you want it to.
Taking all of the above into account, I think your OnTextBoxTextChanged() method should look more like this:
private static void OnTextBoxTextChanged(DependencyObject dObj, DependencyPropertyChangedEventArgs e)
{
ListView listView = dObj as ListView;
if (listView == null) return;
var view = CollectionViewSource.GetDefaultView(listView.ItemsSource);
if (view == null) return;
if (e.NewValue is TextBox)
{
TextBox newValue = (TextBox)e.NewValue;
view.Filter += item =>
{
string filterString = newValue.Text;
// filter based on filterString, etc.
};
textBox.TextChanged += delegate(object sender, TextChangedEventArgs tcea)
{
view.Refresh();
};
}
else if (e.NewValue is string)
{
string filterString = (string)e.NewValue;
view.Filter += item =>
{
// filter based on filterString, etc.
};
}
else return;
}
For this to work, you'll of course have to change the type of your attached property from TextBox to object.
In the above, I did not bother to address the issue of unsubscribing from the TextChanged or Filter events. If you desire to fix that particular problem, it is relatively simple: you need to unsubscribe the old handlers from the events (for TextChanged, only if e.OldValue is TextBox is true of course).
Of course, to do this you'll need to store the old event handler delegate instances on a per-ListView object basis, e.g. in a dictionary or maybe even just having a private attached property (similar to the FilterSource property, but not visible to other code). That way, you can retrieve the delegate instances later to unsubscribe them from their events.
I have a Grid with a ScrollViewer around it. At the top of my ScrollViewer is a Button. On a Click on the Button, I want the ScrollViewer to scroll to a Control at the bottom of the ScrollViewer.
With the following XAML I can bring the Control into view:
<Button Grid.Row="2" Content="Some Button" Command="{Binding DoJumpCommand}" CommandParameter="{Binding ElementName=window}"/>
The Command in the ViewModel is:
if (parameter is MainWindowView)
{
var mainWindowView = parameter as MainWindowView;
mainWindowView.myJumpTarget.BringIntoView();
}
This works fine. But I'm not sure if this is clean MVVM because I pass the complete View into the ViewModel.
Is there a better way to do this?
When I first saw your question, I thought that the general solution to handling events with MVVM is to handle them in an Attached Property. However, looking again, it occurred to me that you're not actually handling any events... you just want to call a method from a UI control. So really, all you need is a way to pass a message from the view model to the view. There are many ways to do this, but my favourite way is to define a custom delegate.
First, let's create the actual delegate in the view model:
public delegate void TypeOfDelegate();
It doesn't need any input parameters, because you don't need to pass anything from the view model to the view, except a signal... your intention to scroll the ScrollViewer.
Now let's add a getter and setter:
public TypeOfDelegate DelegateProperty { get; set; }
Now let's create a method in the code behind that matches the in and out parameters of the delegate (none in your case):
public void CanBeCalledAnythingButMustMatchTheDelegateSignature()
{
if (window is MainWindowView) // Set whatever conditions you want here
{
window.myJumpTarget.BringIntoView();
}
}
Now we can set this method as one (of many) handlers for this delegate in a Loaded event handler in the view code behind:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// Assumes your DataContext is correctly set to an instance of YourViewModel
YourViewModel viewModel = (YourViewModel)DataContext;
viewModel.DelegateProperty += CanBeCalledAnythingButMustMatchTheDelegateSignature;
}
Finally, let's call our delegate from the view model... this is equivalent to raising the event:
if (DelegateProperty != null) DelegateProperty(dataInstanceOfTypeYourDataType);
Note the important check for null. If the DelegateProperty is not null, then all of the attached handler methods will be called one by one. So that's it! If you want more or less parameters, just add or remove them from the delegate declaration and the handling method... simple.
So this is an MVVM way to call methods on a UI control from a view model. However, in your case it could well be argued that implementing this method would be overkill, because you could just put the BringIntoView code into a basic Click handler attached to your Button. I have supplied this answer more as a resource for future users searching for a way to actually call a UI method from a view model, but if you also chose to use it, then great!
I am using LongListSelector control on Windows Phone 8 and can't figure out the best way to handle a tap on an item.
The few examples I've found rely on the SelectionChanged event. However, this solution is buggy because if I tap an item that opens a new page, hit back, and then tap the same item again, it won't work because this item is already selected, so SelectionChanged is not triggered.
I tried to register to the tap event and use the current selected item as the tapped one, but some times the current selected item is not the one I expect.
I could wrap my ItemTemplate in a button and handle the tap for each item but I need to reskin the button to make it look like a simple list item.
Finally, I don't understand why it is so complicated to achieve such a basic thing. Is there a simple and standard way I missed?
My second wish is to get an effect on the item when it is tapped. Is there any standard way to do it?
You could null your LongListSelector's SelectedItem at the end of each SelectionChanged event. I.e.
<phone:LongListSelector x:Name="LLS" SelectionChanged="LLS_SelectionChanged">
And the event handler:
private void LLS_SelectionChanged(object sender, SelectionChangedEventArgs e) {
// If selected item is null, do nothing
if (LLS.SelectedItem == null)
return;
// Navigate to the next page
NavigationService.Navigate(new Uri("/nextpage.xaml", UriKind.Relative));
// Reset selected item to null
LLS.SelectedItem = null;
}
You'll fire the SelectionChanged event twice, but nothing's going to happen the second time round and you should get the behaviour that you're looking for - (i.e Setting SelectedItem to null will trigger a new SelectionChanged event, but this second event gets caught in the if-statement)
As for the second part of your question, you might be better posting a new question.
I done it with the Tap event handling.
I prefer not to use Selected property, but get tapped item this way (and I haven't noticed any bugs):
MyListItemClass item = ((FrameworkElement)e.OriginalSource).DataContext
as MyListItemClass;
Also, you could get the original item ContentPresenter simple by navigating up through VisualTree from e.OriginalSource. That way:
ContentPresenter itemPresenter = SomeHelperClass
.FindParent<ContentPresenter>(e.OriginalSource,"");
Where FindParent is similar to find child in this question: How can I find WPF controls by name or type?
ContentPresenter is that object what you need to manually change the item template if you want to (to set "selected" state for example).
private void Item_tap(object sender, RoutedEventArgs e)
{
var element = (FrameworkElement)sender;
DataSource data = (DataSource)element.DataContext;
}
My second wish is to get an effect on the item when it is tapped. Is
there any standard way to do it?
For this the only thing you need to do add this to your control (or stackpanel where you want to have this effect):
<StackPanel toolkit:TiltEffect.IsTiltEnabled="True">
first add this to *.xaml page inside the
LongListSelectorSelectionChanged="listBox_SelectionChanged"
so that it looks like this :
<toolkit:LongListSelector x:Name="listBox" SelectionChanged="listBox_SelectionChanged">
then in the *.xaml.cs file in the event handler
private void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Write your logic on what you want to do with the selected item
}
In addition to halil´s answer:
First of all you need to install the Windows Phone Toolkit (WPtoolkit) by NuGet.
Than add the namespace declaration on the PhoneApplicationPage.
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
After this you can add toolkit:TiltEffect.IsTiltEnabled="True" to the control definition.
It is nice documneted by NOKIA:
http://developer.nokia.com/community/wiki/Tilt_Effect_for_Windows_Phone
Oliver