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.
Related
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.
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 having trouble with the MVVM pattern and Commands in my WPF app. The problem is not so much the MVVM pattern, but more the stuff that is going on on my GUI. I'll explain the situation:
My app can DoStuff to some files. I have a class with a function DoStuff(int limit). My user user interface has the following items:
A Button DoStuffBtn to start parsing.
A TextBox LimitTxt to fill in a limit.
A CheckBox LimitChk to enabled or disable the limit.
When you would "uncheck" LimitChk, then LimitTxt.Text = "" and LimitTxt.IsEnabled = false. When you would "check" LimitChk, then LimitTxt.IsEnabled = false again, but the text remains empty until you fill something in.
I have read many tutorials on Commands in WPF and MVVM but I just can't seem to pour my case into that mold. The example I gave is actually just a small part of my UI, but I can't seem to do this nicely either.
I keep running into questions like:
Do I need two Commands for LimitChk (enable and disable) or just one (toggle)?
If I bind an int to LimitTxt, what happens if I make it empty and disable it?
Is it a clean way to just use DoStuff(Int32.Parse(LimitTxt.Text)) when DoStuffBtn is pressed?
If I use two commands on LimitChk, what happens with the CanExecute() function of ICommand that determines whether LimitChk is enabled?
So the main question is: How would the situation I described fit into a nice pattern using Commands in WPF?
Some links on WPF, Commands and MVVM i've looked at:
http://www.devx.com/DevX/Article/37893/0/page/1
http://msdn.microsoft.com/en-us/magazine/cc785480.aspx?pr=blog
http://jmorrill.hjtcentral.com/Home/tabid/428/EntryId/432/MVVM-for-Tarded-Folks-Like-Me-or-MVVM-and-What-it-Means-to-Me.aspx
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
What I understand so far is that I have to keep as much as possible out of the UI. Even stuff like UI influencing the UI. I.e. unchecking LimitChk disables LimitText. Still, I think I should keep a difference between UI related information and actions and stuff that actually has to do with the actual work that has to be done.
I think you're getting confused... you don't need any commands here, you can just use bindings.
Do I need two Commands for LimitChk (enable and disable) or just one (toggle)?
You need none. Just create a LimitEnabled property in your ViewModel, and bind the CheckBox to it (IsChecked="{Binding LimitEnabled}")
If I bind an int to LimitTxt, what happens if I make it empty and disable it?
Disabling it has no effect. If you make the TextBox empty, the binding will fail because an empty string can't be converted to an int (at least not with the default converter)
Is it a clean way to just use Parse(Int32.Parse(LimitTxt.Text)) when ParseBtn is pressed?
You don't need to. Just create a Limit property in your ViewModel, and bind the TextBox to it. You might want to add an ExceptionValidationRule to the Binding so that it highlights invalid input.
The button is not necessary, the parsing will be done automatically when the TextBox loses focus (if you use the default UpdateSourceTrigger). If you want to customize the way it's parsed, you can create a custom converter to use in the binding.
Just some high level thoughts, leaving out superfluous stuff like Color and alignment attributes, WrapPanels, etc.
Your ViewModel has a a couple properties:
public bool? LimitIsChecked { get; set; }
public bool LimitTextIsEnabled { get; set; } //to be expanded, below
public ICommand ParseCommand { get; private set; } // to be expanded, below
public string LimitValue { get; set; } // further explanation, below
Your XAML has CheckBox and TextBox definitions something like:
<CheckBox Content="Limit Enabled" IsChecked="{Binding LimitIsChecked}" />
<TextBox Text="{Binding LimitValue}" IsEnabled="{Binding LimitIsEnabled}" />
<Button Content="Parse" Command="{Binding ParseCommand}" />
You'll want to initialize ParseCommand something like this:
this.ParseCommand = new DelegateCommand<object>(parseFile);
Now, let's fill in that LimitTextIsEnabled property too:
public bool LimitTextIsEnabled {
// Explicit comparison because CheckBox.IsChecked is nullable.
get { return this.LimitIsChecked == true; }
private set { }
}
Your parseFile method would then pass the value of the LimitValue property to the logic doing the actual parsing.
I declared the LimitValue property as string here to avoid cluttering up the code with an explicit converter, or other validation code. You could choose to handle that "LimitValue is a valid int" verification/conversion in several different ways.
Of course, I haven't implemented this in its entirety, but I wanted to outline a pattern where you are not using Commands to update the state of the other widgets. Instead, bind those attributes to properties that are managed in your ViewModel.
I did some googling and didn't find an answer to this puzzle.
Provided you have the following:
MySuperView
MySuperViewModel
MySuperView has two textboxes both bound to string properties on the ViewModel
and your using a DelegateCommand to bind your 'Save' button to the ViewModel using syntax such as:
ViewModel:
this.SaveOrderCommand = new DelegateCommand<object>(this.Save, this.CanSave);
View:
Command="{Binding SaveOrderCommand}"
How do you deal with UI Elements to make the User Interaction more pleasing. For example, lets say some lower level failure occurring during the save action of the DelegateCommand and you would like to trigger the tooltip of one of the TextBoxs. How would this typically occur?
I'd like to stick with as clean code-behind as possible but I am not adverse to putting UI specific code in there.
I would recommend that your ViewModel implement IDataErrorInfo so you can take advantage of validation stuff in WPF. You don't need to wait until someone clicks the save button, once the textbox gets updated it will be validated.
public string this[ColumnName]
{
if (Column == "TextProperty")
{
if(!ValidateTextProperty())
return "TextProperty is invalid";
}
}
void Save(object param)
{
if (CanSave)
{
if (string.IsNullOrEmpty(this["TextProperty"])
{
//Add Save code here
}
}
}
In your View:
<TextBox Text={Binding TextProperty, ValidateOnDataErrors="true",
UpdateSourceTrigger=PropertyChanged}/>
This will put a red box around the textbox and you can add a validation error template to the textbox style to add a tooltip see here
To show exceptions in a tooltip, I would add a property to the ViewModel that exposes the error message as a string, and bind that to your TextBox's ToolTip. Then in your Save method, you would start by setting that property to the empty string, and then doing all the real work inside a try..catch that, if an exception occurs, pushes the exception message into that property, so it automatically shows up in the ToolTip.
You would need to provide change notification for your property, either by making it a DependencyProperty or by using INotifyPropertyChanged.
Basically, you would want a create properties for your view to observe (usually through triggers) that would update your UI depending on what is happening in your code execution.