I'm very new to C# and WPF in general, having come from a LAMP web application background. I've tried following a couple tutorials on the subject but they have left me stumped.
https://rachel53461.wordpress.com/2011/05/08/simplemvvmexample/
http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial
In the first tutorial the application opens with a simple interface. A TextBox and two buttons. When you update the ProductId TextBox, going from 0 to 1, the "Get Product" button becomes active. When you click on the "Get Product" button the DataTemplate's contents appear. I can't see where these things are actually happening. What causes the button to become active? What causes the form to appear?
Hopefully you guys and gals can dumb it down for me =)
The buttons become activated or deactivated since they're bound to an ICommand. ICommand includes a CanExecute property, which determines whether the button is active or not.
I can't see where these things are actually happening.
The command uses delegates:
_getProductCommand = new RelayCommand(
param => GetProduct(),
param => ProductId > 0
);
The second delegate causes the command to be activated (CanExecute becomes true) when ProductId > 0 (the delegate returns true).
When you click the button, the command's Execute method fires, and performs the actions.
As for the window starting up in the first place, look at the section titled "Starting the Sample" - there's code in app.xaml.cs which is used to display the Window at first.
In that specific example you can see:
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding ProductId}" />
The TextBox uses data binding to bind its Text property to the ProductId on the current data context (which is an instance of the view model).
When you type text in the TextBox, its value will be automatically updated on the ProductId property on the view model.
The command checks the value of the ProductId is greater than 0 to determine if the Button is enabled.
You can see the Button's command is set to the GetProductCommand:
<Button ... Command="{Binding Path=GetProductCommand}" ... />
The GetProductCommand uses the expression ProductId > 0 as its CanExecute predicate:
_getProductCommand = new RelayCommand(
param => GetProduct(),
param => ProductId > 0
);
WPF will execute this lambda expression ProductId > 0 whose result will determine if the Button is enabled. When the button is enabled and clicked, the first lambda expression will be executed - GetProduct().
Having said all this, you really should be using an MVVM framework, and these frameworks have other mechanisms for invoking methods on your view model which get past some of the limitations of commanding.
Essentially the Idea behind MVVM or Model, View, ViewModel is to remove that code behind and separate out the layers of the application so they can be worked on independently. The interaction between your GUI or View and Viewmodel or codebehind isn't happening like you probably think it is. A lot of people get confused on how the viewmodel actually interacts with the gui or view. I was a winforms dev with the code behind file in which you could easily see event handlers in the code behind and follow the code logically. In your MainWindow Code behind your setting the data context of the XAML gui to the view model like so.
public partial class MainWindow : Window
{
#region Members
SongViewModel _viewModel;
int _count = 0;
#endregion
public MainWindow()
{
InitializeComponent();
// We have declared the view model instance declaratively in the xaml.
// Get the reference to it here, so we can use it in the button click event.
_viewModel = (SongViewModel)base.DataContext;
}
private void ButtonUpdateArtist_Click(object sender, RoutedEventArgs e)
{
++_count;
_viewModel.ArtistName = string.Format("Elvis ({0})", _count);
}
}
Then the {Binding Path=Property} is wiring up your properties of your _viewModel to the XAML Elements. Adding the RaisePropertyChanged is what is notifying the gui that the value of that property has changed.
public string ArtistName
{
get { return Song.ArtistName; }
set
{
if (Song.ArtistName != value)
{
Song.ArtistName = value;
RaisePropertyChanged("ArtistName");
}
}
}
The ArtistName Property of the View model is bound to the label like so in XAML
This is your communication between gui and code more or less. So for instance in your first example when you changed the textbox from 0 to 1, that then updates your ProductID property in your ViewModel. You can see in your ICommand GetProductCommand there is a parameter being passed to relayCommand for ProductID > 0. Now that its 1 canexecute is true so the command is now available to execute and the button becomes clickable. When you Click it GetProduct is the action that takes place and that method then sets your CurrentProduct Property with value. The Datatemplate for product information is bound to ProductModel in the xaml and ProductModel is bound to your CurrentProduct, so now inside that datatemplate the properties of CurrentProduct can be bound to xaml elements so CurrentProduct.ProductName or is bound to
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding ProductName}" />
Its confusing at first but when you get the hang of it, it will make total sense.
Related
I am developing an UWP app leveraging the MVVM paradigm. My view contains a simple TextBox that has its Text property bound to a respective ViewModel property:
<TextBox Text="{Binding Path=Radius, Mode=TwoWay}"/>
Naturally, I've assigned my ViewModel to the page's DataContext:
public sealed partial class ExamplePage : Page
{
private ExamplePageVM viewModel;
public ExamplePage()
{
this.InitializeComponent();
viewModel = new ExamplePageVM();
DataContext = viewModel;
}
}
In the ViewModel I perform some kind of input validation, i. e. if the user inserts an invalid float value into the TextBox I want to reset the TextBox to a default value (zero, for instance):
class ExamplePageVM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private float radius;
public string Radius
{
get => radius.ToString();
set
{
if (radius.ToString() != value)
{
if (!float.TryParse(value, out radius)) radius = 0;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Radius)));
}
}
}
}
Changing the value in the TextBox causes the setter to be called as intended. Also, the PropertyChanged event is invoked accordingly. However, the TextBox still contains invalid data after the setter execution has finished, which means that the view isn't updated correctly.
According to the first comment on this post, the solution to this issue is using <TextBox Text="{x:Bind viewModel.Radius, Mode=TwoWay}"/> instead of the Binding approach shown above. Why is that so? What's the difference between Binding and x:Bind in this very situation?
You may want to set the UpdateTrigger yourself since TextBox normally updates the source when focus lost gets called.
You can change the behaviour UpdateSourceTrigger=PropertyChanged.
<TextBox Text="{x:Bind AnswerText, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding AnswerText, UpdateSourceTrigger=PropertyChanged}"/>
If this is not working you may want to prevent inputs different then numbers with the keydown event. Which you could outsource in a user control for reuse.
Hope this helps.
Binding to TextBox.Text is a rather special case, because Microsoft made a decision where the most common scenario is that the binding should be updated when control loses focus, as opposed to each and every input text change. This allows 2 things:
somewhat more efficient handling of larger texts
safeguarding user input in progress from being changed by application
In the absence of publicly available UWP source code it's possible that MS developers may provide you with more reliable insight, but even comparing the changes to a bound source with direct tracking of the EditBox.TextProperty via DependencyObject.RegisterPropertyChangedCallback makes you expect that instead of the usual direct binding to the dependency property changes there is actually an additional man-in-the-middle kind of implementation in TextBox that handles how and when TextProperty updates affect and being affected by the DataContext or underlying class properties bound with {Binding} or {x:Bind}.
Note that {x:Bind} and {Binding} are very different mechanisms, especially with the first being a compile-time and the 2nd is run-time, meaning that internally they require different implementation and it's up to the framework developers to make sure they exhibit the identical behavior.
Now in your test scenario you are trying to validate and possibly change a property value in the bound data source expecting that the TextBox will display the value you want, which it does with {x:Bind}, but not with {Binding}.
Apparently you've found a scenario where {x:Bind} and {Binding} implementations behave differently. I did the same tests and totally confirm your findings.
I have a dialog which has its DataContext assigned in the code-behind. When a button (somewhere in the UX) is clicked, an asynchronous command is executed. In that command, the dialog is initialized and then gets opened.
The problem now is, that sometimes the ComboBox is empty. Does anyone see, how this could go wrong?
I can print the values from the asynchronous database-access before opening the dialog, and the values are always there.
There are other fields in the dialog, which are bound to the ActiveUser object. That object is not being set from data originating from an async call. Those values are always present, unlike the ComboBox ItemsSource. So I assume, it has something to do with the async call.
Creation and opening of the dialog in a command:
EditUser = AsyncCommand.Create(async (choosenUser) =>
{
// create a dialog, which has its DataContext assigned in the constructor in the code-behind
EditUserDialogView dialog = new EditUserDialogView();
// assign a property (non-async)
((EditUserDialogViewModel)dialog.DataContext).ActiveUser = (DbUser)choosenUser;
// get the list of UserTypes async
List<UserType> userTypeList = await DataAccessService.GetUserTypesAsync();
// Debug output -> usertypes are always printed correctly, so are available at this point
foreach (UserType ut in userTypeList)
{
Log.Info("UT: "+ ut.UserTypeName);
}
// assign UserTypes to property bound to combobox ItemsSource
((EditUserDialogViewModel)dialog.DataContext).UserTypeComboBoxList = userTypeList;
// open the dialog
if (dialog.ShowDialog() == true)
{
}
else
{
}
});
Binding in dialog:
<ComboBox IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding UserTypeComboBoxList, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="UserTypeName"/>
The AsyncCommand is implemented as suggested here:
https://msdn.microsoft.com/en-us/magazine/dn630647.aspx?f=255&MSPPError=-2147217396
(The only difference is, that the parameter for the lambda is the CommandParameter (here choosenUser), not a CancellationToken.)
Here is the property definition in the ViewModel for the dialog:
public List<UserType> UserTypeComboBoxList { get; set;}
I figured out, how I can fix this behavior. When I change the property to raise a NotifyPropertyChanged event, it will always show the ComboBox correctly, containing the values.
public List<UserType> UserTypeComboBoxList
{
get { return userTypeComboBoxList; }
set
{
userTypeComboBoxList = value;
NotifyPropertyChanged(nameof(UserTypeComboBoxList));
}
}
As all values should be initialized by the time I open the dialog, I do not see why raising this event changes anything.
It's the race condition. Since, the database call (to fill comboBox) is invoked asynchronously so if before the dialog initializes, asynchronous call is completed then the comboBox is displayed properly; If not, then you see that you are assigning the new object to property UserTypeComboBoxList (without NotifyingPropertyChanged) which would change the reference of property and in this case, XAML binding loose tracks of it property reference until not notify explicitly which is being done by the later property definition.
So the later code of Property definition is correct. The other way of achieving the same behavior is using following (if the bound data can be changed during the program execution):
Use the ObservableCollection which provides the notification
when object is added/deleted into collection
Instead of initializing a new object for property
UserTypeComboBoxList in your action, initialize at Constructor,
and Clears & Add the updated values everytime.
The problem seems to be, that once I create the dialog and InitializeComponent() is called, C# starts to assemble the final View class (asynchronously) and initializes the bindings. So my assignments of the properties of the ViewModel will either by luck be the initial assignment, because it was not already bound, and sometimes it will be an update of the already bound empty initial value.
This can be prevented by initializing the ViewModel before assigning it to the View.
I’m developing a WPF application in MVVM Patern. The application has a command bar and buttons for Save and Delete records.
The application also has a Master Detail form. It’s a User control and a DataGrid.
Master block : Customer Order
Detail block: Customer Order Lines
(one to many relationship).
Problem:
When clicking a button in command bar, different actions need to be performed depending on the focused item.
For an example if I click the Delete button
It should delete the records only in the DataGrid row, when DataGrid has
focus and row(s) selected.
E.g. DeleteRows() Method should be called.
It should delete the entire record if the master block has focus and not datagrid focused.
E.g. DeleteRecord() Method should be called.
As far as I know I can achieve this using Keyboard focus and Logical focus manager.
But I was unable to find out a proper solution. I should consider that, when clicking the delete button I should ignore the focus of the Delete button.
Please help me to overcome this issue with a sample code.
Since you're using the MVVM pattern, I assume that your buttons in the command bar have corresponding ICommands in the view model.
You can bind your DataGrid's SelectedItem property to a view model property (of course, with a two-way binding) and make that decision according to this property value. If it is null, so there's no item currently selected in the DataGrid, and you can delete the whole record. If it is set to an instance, then a row is selected in the DataGrid, and you can delete only one row.
If you need to exactly know which was the last focused element, you can use the Keyboard.PreviewLostKeyboardFocus attached event in your code behind. Or even better, create your own Behavior with a dependency propery that you can bind to your view model.
enum LastFocusedEntityType { None, Record, Row }
class LastFocusedEntityTrackingBehavior : Behavior<UIElement>
{
public static readonly LastFocusedEntityProperty = DependencyProperty.Register(
"LastFocusedEntity",
typeof(LastFocusedEntityType),
typeof(LastFocusedEntityTrackingBehavior),
LastFocusedEntityType.None);
public LastFocusedEntityType LastFocusedEntity
{
get { return (LastFocusedEntityType)this.GetValue(LastFocusedEntityProperty); }
set { this.Setvalue(LastFocusedEntityProperty, value); }
}
protected override void OnAttached()
{
Keyboard.AddPreviewLostKeyboardFocusHandler(this.AssociatedObject, this.PreviewLostKeyboardFocusHandler);
}
private void PreviewLostKeyboardFocusHandler(object sender, KeyboardFocusChangedEventArgs e)
{
if (e.OldFocus is DataGrid)
{
this.LastFocusedEntity = LastFocusedEntityType.Row;
}
else
{
this.LastFocusedEntity = LastFocusedEntityType.Record;
}
}
}
Then you can apply this behavior to your master block container:
<UserControl>
<i:Interaction.Behaviors>
<local:LastFocusedEntityTrackingBehavior LastFocusedEntity="{Binding LastFocusedEntity, Mode=OneWayToSource}"/>
</i:Interaction.Behaviors>
</UserControl>
In your view model, your ICommand's Execute() method should then look at the LastFocusedEntity property value and decide what to do next.
Note: I didn't check this code whether it compiles.
I am using the Telerik RadRibbonView in my WPF 4.5 project. The set up looks like this.
In my Shell I have a RibbonView and a TabControl defined as a regions called “RibbonRegion” and “TabRegion”. The RibbonRegion is basically the menu of the application and the TabRegion holds the main content.
I have also created a module with a View containing a RibbonTab and a RibbonButton. This button is hocked up to a command that sets the DataContext of a RibbonContextTabView and a TabItemView and registers them in their respective regions. The ContextTab and the TabItem is sharing the same ViewModel. This ViewModel has a propery “IsSelected” that the ContextTab and TabItem are bound to.
if (_regionManager.Regions["RibbonRegion"].Views.Any(v => v.GetType() == typeof(ContextTabView)) && _regionManager.Regions["TabRegion"].Views.Any(v => v.GetType == typeof(TabItemView)))
{
_regionManager.RequestNavigate("RibbonRegion", new Uri("ContextTabView", UriKind.Relative));
_regionManager.RequestNavigate("TabRegion", new Uri("TabItemView", UriKind.Relative));
}
else
{
ContextTabView contextTabView = _container.Resolve<ContextTabView>();
TabItemView tabItemView = _container.Resolve<TabItemView>();
contextTabView.DataContext = tabItemView.DataContext = new ContextTabTabItemViewModel();
_regionManager.RegisterViewWithRegion("RibbonRegion", () => contextTabView);
_regionManager.RegisterViewWithRegion("TabRegion", () => tabItemView);
}
The first time the Command above is executed the DataContext of the views is set and then they are registered in the regions. This also sets the “IsSelected” property to true. If I change focus to the RibbonTab my ContextTab and TabItem loses focus and the “IsSelected” propery is set to false. If I press the button again the RequestNavigate is executed and once again the property is set to true. Here is my problem. If I do this a third time nothing happens! The RequestNavigate is executed but the property is not set to true and the Views does not regain focus. I am fairly new to PRISM and I am afraid that I am way off here. Any help would be appreciated.
In order to keep communication between ViewModels in a loosely coupled manner, you could simply use the EventAggregator and raise an event from the Command Button implementation, which would be then handled by the TabItemViewModel.
The solution you mentioned by adding one ViewModel into another would not be ideal as these components would end up working with tight coupling and defining an incorrect situation as Views/ViewModels would not depend on another View.
Therefore, to accomplish the EventAgregation approach, you would need to receive the EventAggregator from the container throw constructor on the View/ViewModel where the button is clicked, and on each one of the ViewModels you would want to subscribe to that event setting the IsSelected property inside the EventHandler method.
You could subscribe to the "GiveFocusEvent" event and handle it on the ViewModels which would set their IsSelected property as shown below:
public TabItemViewModel(IEventAggregator eventAggregator, ..){
...
GiveFocusEvent setFocusEvent = eventAggregator.Get<GiveFocusEvent>();
setFocusEvent.Subscribe(SetFocusEventHandler, ThreadOption.UIThread);
}
public void SetFocusEventHandler(){
// change IsSelected property value..
}
The Event would be published from inside the Button's CommandHandler method as follows:
this.eventAggregator.GetEvent<GiveFocusEvent>().Publish();
Notice that you would need to create and make your "GiveFocusEvent" event class inherit from CompositePresentationEvent:
public class GiveFocusEvent : CompositePresentationEvent<string>{}
I hope this helped you,
Regards.
I have a ComboBox in a WPF app that has recently been refactored to use the MVVM pattern. An apparent side effect to this change is that changing focus to another application while the combobox dropdown is visible completely prevents the dropdown from being visible again, until the app has been restarted.
The ComboBox DataContext is set to my ViewModel, with its ItemsSource bound to an ObservableCollection<String> SearchSuggestions, and IsDropdownOpen bound to a property SuggestionsVisible in the ViewModel.
The desired effect is a search box with autocomplete suggestions. It should close if there are no suggestions in the ObservableCollection, if the user cancels the search, if the user runs the search, or if the user clicks away from the text field - either inside the app or outside it.
The ViewModel explicitly sets the SuggestionsVisible property to true or false based on whether SearchSuggesions contains any items after user input. This process continues to take place after this bug manifests itself, just with no visible change to the UI. Any idea why losing focus while the dropdown is open renders the dropdown un-openable for the rest of the app's session?
Here's how I have things wired together:
<ComboBox DataContext="{Binding SearchBoxVm}" Name="cmboSearchField" Height="0.667"
VerticalAlignment="Top" IsEditable="True" StaysOpenOnEdit="True"
PreviewKeyUp="cmboSearchField_OnKeyUp"
PreviewMouseLeftButtonUp="cmboSearchField_OnPreviewMouseLeftButtonUp"
Background="White" ItemsSource="{Binding SearchTopics}"
IsDropDownOpen="{Binding SuggestionsVisible,
UpdateSourceTrigger=PropertyChanged}"
Margin="50.997,15.333,120.44,0"
RenderTransformOrigin="0.5,0.5" Grid.Row="1" >
<!-- SNIP STYLING -->
</ComboBox>
ViewModel:
public class SearchBoxViewModel : INotifyPropertyChanged
{
public void ResetSearchField(bool preserveContents = false)
{
if (!preserveContents || string.IsNullOrEmpty(Query))
{
Foreground = Brushes.Gray;
QueryFont = FontStyles.Italic;
Query = DEFAULT_TEXT;
}
}
public bool OnKeyUp(Key key)
{
bool showDropdown = SuggestionsVisible;
bool changeFocusToCombobox = false;
if (keyInValidRange(key))
{
SearchSuggestions = GetSearchSuggestions(Query);
if (SearchSuggestions.Count > 0)
{
SuggestionsVisible = true;
}
}
return changeFocusToCombobox;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
bool _suggestionsVisible = false;
public bool SuggestionsVisible
{
get { return _suggestionsVisible; }
set
{
// this section is still called after this issue manifests,
// but no visible change to the UI state is made
_suggestionsVisible = value;
NotifyPropertyChanged("SuggestionsVisible");
}
}
public ObservableCollection<String> SearchTopics = new ObservableCollection<String>();
}
The OnKeyUp() method is called by the MainWindow class ( haven't gotten as far as binding events to handlers specified in the ViewModel ), while but there's also a call to ResetSearechField from the MainWindow:
// Note: removing references to this event handler does not have any effect
// on the issue at hand... only including here for completeness
void window_Deactivated(object sender, EventArgs e)
{
SearchBoxVm.SuggestionsVisible = false;
SearchBoxVm.ResetSearchField(true);
}
I've spent quite a bit of time trying to debug this, and haven't seen any internal state changes that might account for this. The NotifyPropertyChanged event is otherwise behaving as it did before, and the stack trace window isn't showing any exceptions having been encountered.
Setting the binding mode on the IsDropdownOpen property to 'TwoWay' in the XAML hasn't had any effect either. Lastly, wrapping the assignment to SuggestionsVisible in a Dispatcher call on the main thread has had no effect on the issue either.
Any assistance would be appreciated.
#BrMcMullin, since you have stated that:
The desired effect is a search box with autocomplete suggestions.
may I ask, why do you choose to use standard ComboBox instead of specialized AutoCompleteBox that is available in the WPF Toolkit - February 2010 Release and seems like was especially designed for your case?
You may have noticed that first link points to documentation for its Silverlight predecessor, but don't worry - WPF Toolkit library include fully functional official WPF port of AutoCompleteBox from Silverlight. There is more info about this "event": AutoCompleteBox: Now with 100% more WPF.
With that control your auto complete popup could looks as simple as:
or as complex as:
So, if you will not manage to solve your issue with ComboBox's popup visibility, feel free to give a try to AutoCompleteBox. With it you could even leverage dynamic sorting of your suggestions if needed (just use answer from #adabyron).