I have a TextBox and a Button in my view. I have an ICommand method, String serial number property, and a IsEnabled property in my view model.
When the user clicks the Button I'd like to validate the serial number in the TextBox with the InDatabase property. If the contents in the TextBox are invalid, I would like to raise an error on the TextBox. If the contents are valid in the TextBox I'd like to disable the Button and execute the command.
Here is the view:
<StackPanel Width="Auto" Height="Auto" VerticalAlignment="Center" HorizontalAlignment="Center">
<UniformGrid Rows="3" >
<TextBlock Text="This device appears to be uninitialized."/>
<UniformGrid Rows="1" Columns="2">
<Label>Serial Number:</Label>
<TextBox Text="{Binding IdentifiedSerialNumber, Mode=TwoWay, ValidatesOnDataErrors=True}"></TextBox>
</UniformGrid>
<Button Content="Identify" Command="{Binding IdentifyCommand}" IsEnabled="{Binding CanExecuteDeviceRestoration}"/>
</UniformGrid>
</StackPanel>
Here is the view-model:
public string IdentifiedSerialNumber
{
get
{
return this.identifiedSerialNumber;
}
set
{
this.identifiedSerialNumber = value;
}
}
public ICommand IdentifyCommand
{
get
{
return new RelayCommand(this.RelayRestoreControllerIdentity);
}
}
public bool CanExecuteDeviceRestoration
{
get
{
return canExecuteDeviceRestoration;
}
private set
{
this.canExecuteDeviceRestoration = value;
RaisePropertyChanged("CanExecuteDeviceRestoration");
}
}
public async void RelayRestoreControllerIdentity()
{
await Task.Run(
() =>
{
this.RestoreControllerIdentity();
});
}
public bool InDatebase
{
get
{
return DatabaseConnection.DeviceExists(this.IdentifiedSerialNumber);
}
}
My question is how do I bind the behavior such that when the user click the Button the TextBox is validated, and if it fails it displays an error with a message and if it passes the Button will be disabled and the command will execute.
You need to implement IDataErrorInfo.
This would add an indexer which returns a string.
If empty string is returned it means no error.
You can return an empty string until button is pressed(u could use a flag).
when button is pressed,Run the validation logic and appropriately change the flag and Raise PropertyChanged event for IdentifiedSerialNumber
You can learn how to implement IDataErrorInfo from here.
Also you need to Raise PropertyChanged event for IdentifiedSerialNumber.
Related
<TextBox Grid.Row="1" Grid.Column="2" Width="50" Text="{Binding Size}"></TextBox>
<ComboBox Grid.Row="2" Grid.Column="1" ItemsSource="{Binding MyList, Mode=OneTime}"
SelectedValue ="{Binding ListSelectedItem, Mode=TwoWay}"></ComboBox>
<Button Grid.Row="3" Grid.Column="2" Content="Save" Command="{Binding SaveCommand}" IsEnabled="False"/>
ViewModel,
public ICommand SaveCommand { get; }
public ConfigurationViewModel()
{
SaveCommand = new RelayCommand(SaveCommandExecute);
}
public int Size
{
get
{
return _size;
}
set
{
_size = value;
OnPropertyChanged();
}
}
private void SaveCommandExecute()
{
// save logic
}
I can able to save the data entered in textbox and selected value in combo box. By default, Save button should be disabled and if any change in textbox / combobox then enable the Save button.
if user reverts back to old value in textbox / combobox then Save button should be disabled back.
In yours RelayCommand might be a second parameter of Func<object, bool> canExecute. Then you can add method for checking if value has changed.
private bool CanSaveCommandExecute(object parameter) => this.Size != defaultValue;
And in your constructor add name of that method in SaveCommand as second parameter. I hope this will help you.
My form has 2 radio buttons. When you press either one of them it executes a command that sets a string variable with the relative value for radio button set to true - this also has a CommandParameter that sends the value of the string Content into the Exectue function.
This works fine when a human is pressing a radio button. Variables get set and all is good.
However, I have coded in the xaml for one of the radio buttons to be set to checked by default - and this does not cause the command to be executed on startup of the form for the very first time. Hence the string variable that I hold the appropriate value for the radio button that is checked, never gets set.
How do I get my string variable recieve the value on startup from the Execute(param) method?
Here is the xaml:
<StackPanel Grid.Row="4" Grid.Column="1" Margin="3" Orientation="Horizontal">
<RadioButton GroupName="LcType" Name="TsMapPane" HorizontalAlignment="Left" Checked="TsMapPane_Checked" IsChecked="True"
Command="{Binding Path=LcTypeCommand}" CommandParameter="{Binding ElementName=TsMapPaneTextBox, Path=Text}" >
<RadioButton.Content>
<TextBlock Name="TsMapPaneTextBox" Text="TS_MAP_PANE"/>
</RadioButton.Content>
</RadioButton>
<RadioButton GroupName="LcType" Margin="10 0 0 0" Name="TsGroup" HorizontalAlignment="Left" Checked="TsGroup_Checked"
Command="{Binding Path=LcTypeCommand}" CommandParameter="{Binding ElementName=TsGroupTextBox, Path=Text}">
<RadioButton.Content>
<TextBlock Name="TsGroupTextBox" Text="TS_GROUP"/>
</RadioButton.Content>
</RadioButton>
</StackPanel>
Here is the ViewModel:
public ICommand LcTypeCommand { get; set; }
public MyViewModel()
{
LcTypeCommand = new RelayCommand((param) => LcTypeExecute(param), () => true);
}
private void LcTypeExecute(object param)
{
LcTypeName = param.ToString();
}
public string LcTypeName
{
get => _lcTypeName;
set => SetField(ref _lcTypeName, value);
}
The command is called only when the user clicks on the button.
Changing the state of the RadioButton raises the Checked and Unchecked events. You can connect a command to the Checked event, but there is no guarantee that the IsChecked property will be changed after the listener is connected. Since both are specified in XAML.
In my opinion, the most correct would be to call the command in Code Behind after XAML initialization.
InitializeComponent();
if (TsMapPane.Command is ICommand command &&
command.CanExecute(TsMapPane.CommandParameter))
{
command.Execute(TsMapPane.CommandParameter);
}
P.S. You can add the following extension method to the Solution:
public static partial class WinHelper
{
public static void TryExecute(this ICommandSource commandSource)
{
if (commandSource.Command is not ICommand command)
return;
if (command is RoutedCommand routedCommand)
{
IInputElement? target = commandSource.CommandTarget ?? commandSource as IInputElement;
if (routedCommand.CanExecute(commandSource.CommandParameter, target))
{
routedCommand.Execute(commandSource.CommandParameter, target);
}
}
else
{
if (command.CanExecute(commandSource.CommandParameter))
{
command.Execute(commandSource.CommandParameter);
}
}
}
}
Then the code will be simplified to this:
InitializeComponent();
TsMapPane.TryExecute();
shows a null value for "TsMapPane.CommandParameter"
At the time of XAML initialization, if the DataContext is assigned by an external container, bindings to the DataContext will not yet work.
Therefore, you need to execute the command once in the Loaded event:
public SomeWindow()
{
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
Loaded -= OnLoaded;
TsMapPane.TryExecute();
}
I have a listview which on mouse enter to a particular column, i try to launch a popup in viewmodel class by setting isOpen to true in MyAction2() function which gets called on when user enters mouse on that column of listview.
I observe that when the mouse-enter to that column.It calls my function (MyAction2() function in ViewModel, see code written below) but even on setting the isopen variable to true in MyAction2(), The set-get method of binded isOpen not get called. Now i feel there is problem in binding. Which normally should be correct i feel some thing is missing but i dont know what.
My Xaml (containing teh opup and the column in ListView which on mouse enter calls an event called MyAction2() in ViewModel):
<Grid>
<StackPanel>
<Popup Margin="10,10,0,13" Name="Popup1" IsOpen="{Binding PopUpLaunched,Mode=TwoWay}" Placement="Top" PopupAnimation="Fade" StaysOpen="True" HorizontalAlignment="Left" VerticalAlignment="Top" Width="194" Height="200" MinWidth="500" MinHeight="500">
<StackPanel>
<Border Background="Red">
<TextBlock Name="McTextBlock" Background="LightBlue"> This is popup text </TextBlock>
</Border>
</StackPanel>
</Popup>
</StackPanel>
</Grid>
ViewModel.cs
private bool popUpLaunched;
public bool PopUpLaunched {
get {
return popUpLaunched;
} //Get set never gets called even after the popUpLaunched=true in the MyAction2() call
set {
popUpLaunched = value;
OnPropertyChanged("PopUpLaunched");
}
}
private void MyAction2(object param) //The function which gets called on mouse event but do not pop ups the popup
{
popUpLaunched = true;
}
Whats wrong and where is wrong ?
You should set the PopupLaunched property instead of setting the popUpLaunched field for the setter to get called and the PropertyChanged event to get raised:
private void MyAction2(object param)
{
PopUpLaunched = true;
}
In order to implement such a binding, you can make that property a Dependency property like this
public static readonly DependencyProperty PopUpLaunched = DependencyProperty.Register(
"popUpLaunched", typeof(bool), typeof(MainPage), new PropertyMetadata(null));
public bool popUpLaunched
{
get { return (bool)GetValue(PopUpLaunched); }
set { SetValue(PopUpLaunched, value); }
}
If you are not working on the MainPage, change that typeof(MainPage) argument respectively. And adjust getter and setter for your needs.
My main page has the appbar and it is shared across different pages. I wrote the following code to open the appbar on the click of a gridview item.
XAML
<AppBar Opened="AppBar_Opened" IsOpen="{Binding IsAppBarOpen}">
Back end
private void Clock_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
App.ViewModel.SelectedClock = (Clock)ThemeGridView.SelectedItem;
App.WorldViewModel.IsAppBarOpen = true;
}
private void ThemeGridView_ItemClick(object sender, ItemClickEventArgs e)
{
App.ViewModel.SelectedClock = (Clock)ThemeGridView.SelectedItem;
App.WorldViewModel.IsAppBarOpen = true;
}
WorldViewModel
private bool _IsAppBarOpen;
public bool IsAppBarOpen
{
get { return _IsAppBarOpen; }
set { base.SetProperty(ref _IsAppBarOpen, value); }
}
GridView XAML
<GridView
Grid.Row="1"
Grid.Column="1"
x:Name="ThemeGridView"
ItemsSource="{Binding Clocks}"
ItemTemplate="{StaticResource WorldClockTemplate}"
SelectionChanged="Clock_SelectionChanged"
SelectionMode="None"
IsItemClickEnabled="True"
ItemClick="ThemeGridView_ItemClick"
>
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<WrapGrid />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
</GridView>
But the appbar is not popping up when i select the gridview item. There is no binding error so its really mysterious!
There is not way to bind IsOpen property according the msdn:
Note Binding to the IsOpen property doesn't have the expected results
because the PropertyChanged notification doesn't occur when the
property is set.
<AppBar Opened="AppBar_Opened" IsOpen="{Binding IsAppBarOpen, **Mode=TwoWay**}">
This works for me. I use MVVM Light Toolkit.
public bool AppBarIsOpen
{
get { return this._appBarIsOpen; }
set
{
if (this._appBarIsOpen == value) { return; }
this._appBarIsOpen = value;
this.RaisePropertyChanged("AppBarIsOpen"); // without INotifyPropertyChanged it doesn't work
}
}
<AppBar
IsSticky="True"
IsOpen="{Binding Path=AppBarIsOpen, Mode=TwoWay}">
Roman Weisert's answer correctly states the likely reason for it not working, although you also must make the binding two-way as Zack Weiner suggested (I'm not sure the reason for the latter since the binding is not working in the target-to-source direction anyway). The current value of AppBar.IsOpen may not be reflected by IsAppBarOpen of your view-model. When that's the case, and you try updating the value, it's possible that no PropertyChanged event is raised since you may not actually be updating a value. Instead, you may be just setting the value from false to false or from true to true. Most SetProperty method implementations do not raise the PropertyChanged event unless there is an actual change, and I presume yours is the same.
To fix the problem, consider modifying your view-model as follows:
public bool IsAppBarOpen
{
get { return _IsAppBarOpen; } //changes initiated from UI not reflected
set //not updated from UI
{
_IsAppBarOpen = value;
base.OnPropertyChanged();
}
}
bool _IsAppBarOpen;
The notable difference from your view-model's code, is that SetProperty is not called here so PropertyChanged is raised even when the backing store equals the newly introduced value. In case your base class differs, note that mine has an OnPropertyChanged method with the signature
void OnPropertyChanged( [CallerMemberName] string propertyName = null )
that serves to raise the PropertyChanged event.
I can see from your use of the code-behind, though, that you are not really following MVVM. If MVVM is not a concern to you, then you could forgo the IsAppBarOpen property altogether and just directly set AppBar.IsOpen. As someone who religiously adheres to MVVM, however, I do not recommend that you further head in that (sinful) direction.
I had the same issue and using Caliburn Micro for WinRT and with this code worked for me:
<AppBar IsOpen="{Binding AppBarsOpen}" Name="MainAppBar" Padding="10,0,10,0" AutomationProperties.Name="Bottom App Bar">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50*" />
<ColumnDefinition Width="50*" />
</Grid.ColumnDefinitions>
<StackPanel x:Name="LeftPanel" Orientation="Horizontal" Grid.Column="0" HorizontalAlignment="Left">
<Button Name="ShowFlyout" Style="{StaticResource BookmarksAppBarButtonStyle}" />
</StackPanel>
<StackPanel x:Name="RightPanel" Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Right">
<Button Style="{StaticResource SaveAppBarButtonStyle}" />
</StackPanel>
</Grid>
</AppBar>
And that's your property in ViewModel:
public bool AppBarsOpen
{
get { return _appBarsOpen; }
set
{
if (value.Equals(_appBarsOpen)) return;
_appBarsOpen = value;
NotifyOfPropertyChange(() => AppBarsOpen);
}
}
Had the same issue, solved it by adding the Closed event and updating the ViewModel from the code behind. Saw no other way since TwoWay binding was not working as Roman pointed out.
XAML
<AppBar x:Name="BottomAppBar1"
AutomationProperties.Name="Bottom App Bar"
Closed="BottomAppBar1_Closed"
IsOpen="{Binding IsOpen, Mode=TwoWay}"
IsSticky="True">
C# Code behind
private void BottomAppBar1_Closed(object sender, object e)
{
MainViewModel vm = this.DataContext as MainViewModel;
vm.IsOpen = false;
}
C# MainViewModel
public const string IsOpenPropertyName = "IsOpen";
private bool isOpen = false;
/// <summary>
/// Sets and gets the IsOpen property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public bool IsOpen
{
get
{
return isOpen;
}
set
{
RaisePropertyChanging(IsOpenPropertyName);
isOpen = value;
RaisePropertyChanged(IsOpenPropertyName);
}
}
You should bind both IsOpen and IsSticky two way because otherwise you will get problems with for example having to tap two time to unselect an item (once to close the app bar and once for unselecting) and also it's the will help having your app bar behave more standarly (will prevent the app bar to pop down on tap when an item is selected).
To show the app bar you will need to do the following (the order is important):
this.IsAppBarSticky = true;
this.IsAppBarOpen = true;
and to hide it, do the following:
this.IsAppBarSticky = false;
this.IsAppBarOpen = false;
Another way to make this work without having to use a codebehind handler for app bar closed event:
public class AppBarClosedCommand
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand),
typeof(AppBarClosedCommand), new PropertyMetadata(null, CommandPropertyChanged));
public static void SetCommand(DependencyObject attached, ICommand value)
{
attached.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject attached)
{
return (ICommand)attached.GetValue(CommandProperty);
}
private static void CommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// Attach click handler
(d as AppBar).Closed += AppBar_onClose;
}
private static void AppBar_onClose(object sender, object e)
{
// Get GridView
var appBar = (sender as AppBar);
// Get command
ICommand command = GetCommand(appBar);
// Execute command
command.Execute(e);
}
}
then in the XAML you can use it like :
common:AppBarClosedCommand.Command="{Binding AppBarClosedCommand}"
with the command function looking like:
public void OnAppBarClosed()
{
AppBarOpen = false;
}
I'm rewriting a project in WPF using Caliburn framework. I came from C++ world, so have some difficulties figuring out even simplest things...
So, let's say I have: MainView, MainViewModel, DialogView, DialogViewModel.
In MainView.xaml:
...
<MenuItem Name="Dialog" Header="Dialog"></MenuItem>
...
Caliburn bounds it to a method in MainViewModel:
public void Dialog()
{
dynamic settings = new ExpandoObject();
settings.WindowStartupLocation = WindowStartupLocation.Manual;
_windowManager.ShowWindow(new DialogViewModel(_windowManager), null, settings);
}
It works fine, Dialog pops up.
Now, in this dialog I have:
<TextBox Name="Dimension1"/>
<TextBox Name="Dimension2"/>
plus, other textboxes, checkboxes etc.
Then there are OK and Cancel buttons:
<Button Content="OK" Name="OK"></Button>
<Button Content="Cancel" Name "Cancel"></Button>
Now, as it is right now they are bound to OK() and Cancel() methods in DialogViewModel and I cannot figure out or find information on how to deal with them in DialogViewModel.
I found an example when DialogResultsAction class is created, I can bound my OK/Cancel buttons with the methods in this class, but can't understand how to proceed further...
Can you advice me what direction should I go?
I'm not sure if this is what you're looking for but you can treat the DialogViewModel like any other screen. In this example Ok and Cancel get bound to the respective methods. Technically you could set x:Name="TryClose" for the cancel button's name but I named it Cancel for this example.
In the Open method in ShellViewModel you can preset values on the dialog before you display it. And after the result is returned since you have a reference to it you can also read those values.
Open method in ShellViewModel:
public void Open()
{
var dialogViewModel = IoC.Get<DialogViewModel>();
dialogViewModel.Dimension1 = "123";
dialogViewModel.Dimension2 = "456";
var result = WindowManager.ShowDialog(dialogViewModel);
if (dialogViewModel.MyDialogResult == DialogResult.OK) return;
//do stuff with results
var dim1 = dialogViewModel.Dimension1;
}
DialogView:
<Grid>
<StackPanel>
<TextBlock x:Name="Dimension1" />
<TextBlock x:Name="Dimension2" />
</StackPanel>
<StackPanel Height="50"
Orientation="Horizontal">
<Button x:Name="Ok"
Content="Ok" />
<Button x:Name="Cancel"
Content="cancel" />
</StackPanel>
</Grid>
DialogViewmodel:
[Export(typeof (DialogViewModel))]
public class DialogViewModel : Screen
{
private string _dimension1;
public string Dimension1
{
get { return _dimension1; }
set
{
_dimension1 = value;
NotifyOfPropertyChange(() => Dimension1);
}
}
private string _dimension2;
public string Dimension2
{
get { return _dimension2; }
set
{
_dimension2 = value;
NotifyOfPropertyChange(() => Dimension2);
}
}
public void Ok()
{
//Do stuff
MyDialogResult = DialogResult.OK;
TryClose();
}
public void Cancel()
{
MyDialogResult = DialogResult.Cancel;
TryClose();
}
}
If your ViewModel is based off of IScreen, use Close in the Ok or Cancel method. If you need to return a result, I'd suggest using the IEventAggregator to push the result back to the parent.
If you really need to, you can use GetView() to get the view associated with the viewmodel, cast it to the right type, and set the result (assuming the view has a result, I've not used the Dialog class).