I'm new to WPF following an attempt to move from winforms so this is basic question however i'm trying to add to a string from a button click. The scenario is basically a keypad full of buttons (0 - 9) and the user presses these as to enter a code as a type of PIN. The method of going about this is non-negotiable so i cannot just replace with a text box and have the user type this. I have a little knowledge on bindings however it appears to be the appending to a string which is throwing me out as, obviously, all the buttons (0-9) need to be able to append to this string in sequence. So anyway, i have a ViewModel with a property called 'EnteredCode' and the buttons reside in a grid named 'buttonsGrid'. Would i be correct in thinking i handle the ButtonBase.Click event at the Grid level, determine which one was clicked and then append to the string? The appending to the string is obviously the problem here which i need help with but just as general feedback to best practise!
Code examples would also be a huge help.
TIA
So, you can treat WPF just as windows forms and solve this in the codebehind MainWindow.xaml.cs
Example:
DemonstrationViewModel demoViewModel;
public MainWindow()
{
InitializeComponent();
demoViewModel = new DemonstrationViewModel();
DataContext = demoViewModel;
}
private void alsoDemoButton_Click(object sender, RoutedEventArgs e)
{
demoViewModel.EnteredCode += "Clicked";
}
However, with the mention of ViewModel in your statement, you are likely following a MVVM pattern and writing in the code behind is not recommended.
If you are following a MVVM pattern, ICommands would be one way to go.
Pseudocode-ish example
XAML
<Button x:Name="demoButton" Command="{Binding InsertCommand}"/>
ViewModel
#region Constructor
public DemonstrationViewModel()
{
InsertCommand = new RelayCommand(ExecuteInsert, CanExecuteInsert);
}
#endregion
private void ExecuteInsert()
{
EnteredCode += "Clicked! ";
}
Further reading on ICommand in MVVM
You must be trying to bind Password property of PasswordBox to your ViewModel property. Password property is not bindable as it is not a DependencyProperty. This is for security reasons.
However if you want to make it bindable, you have to use a custom AttachedProperty. Now, you are trying to input PIN by pressing buttons like in ATM machines, and want your password bindable too. See a sample below to get you started.
xaml code :
<Window x:Class="WpfEvents._32802407.Win32802407"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pwd="clr-namespace:PasswordExtras"
Title="Win32802407" Height="354.136" Width="385.714">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="143*"/>
<ColumnDefinition Width="46*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="16*"/>
<RowDefinition Height="19*"/>
<RowDefinition Height="32*"/>
<RowDefinition Height="95*"/>
</Grid.RowDefinitions>
<TextBlock TextWrapping="Wrap" Text="Welcome Joshua !" FontSize="18" VerticalAlignment="Center" Margin="10,4,0,4"/>
<PasswordBox x:Name="pbPin" pwd:PasswordBoxAssistant.BindPassword="True" pwd:PasswordBoxAssistant.BoundPassword="{Binding Path=PIN, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" Grid.Row="2" VerticalAlignment="Center" Width="120" FontSize="18" BorderThickness="0"/>
<StackPanel Grid.Column="1" HorizontalAlignment="Left" Height="170" Margin="10,10,0,0" Grid.Row="3" VerticalAlignment="Top" Width="72">
<Button Content="Done" Margin="0,15,0,0" Height="31"/>
<Button Content="Clear" Margin="0,15,0,0" Height="31"/>
<Button Content="Cancel" Margin="0,15,0,0" Height="31"/>
</StackPanel>
<WrapPanel ButtonBase.Click="NumericButtons_Click" HorizontalAlignment="Left" Height="147" Margin="10,23,0,0" Grid.Row="3" VerticalAlignment="Top" Width="266">
<Button Content="1" Width="75" Margin="5" Height="25"/>
<Button Content="2" Width="75" Margin="5" Height="25"/>
<Button Content="3" Width="75" Margin="5" Height="25"/>
<Button Content="4" Width="75" Margin="5" Height="25"/>
<Button Content="5" Width="75" Margin="5" Height="25"/>
<Button Content="6" Width="75" Margin="5" Height="25"/>
<Button Content="7" Width="75" Margin="5" Height="25"/>
<Button Content="8" Width="75" Margin="5" Height="25"/>
<Button Content="9" Width="75" Margin="5" Height="25"/>
<Button Content="0" Width="75" Margin="5" Height="25"/>
</WrapPanel>
<TextBlock HorizontalAlignment="Left" Margin="23,9,0,0" Grid.Row="1" TextWrapping="Wrap" Text="Enter your pin or press cancel" VerticalAlignment="Top"/>
</Grid>
</Window>
xaml code-behind :
using System;
using System.Windows;
using System.Windows.Controls;
using System.Diagnostics;
namespace WpfEvents._32802407
{
/// <summary>
/// Interaction logic for Win32802407.xaml
/// </summary>
public partial class Win32802407 : Window
{
ViewModelATM atm = new ViewModelATM();
public Win32802407()
{
InitializeComponent();
this.DataContext = atm;
}
private void NumericButtons_Click(object sender, RoutedEventArgs e)
{
string pwd = PasswordExtras.PasswordBoxAssistant.GetBoundPassword(pbPin);
if (pwd.Length == 4)
{
e.Handled = true;
return;
}
pwd = pwd + ((Button)e.OriginalSource).Content;
PasswordExtras.PasswordBoxAssistant.SetBoundPassword(pbPin, pwd);
Debug.WriteLine(pwd + " : " + atm.PIN);
}
}
}
namespace PasswordExtras
{
public static class PasswordBoxAssistant
{
public static readonly DependencyProperty BoundPassword =
DependencyProperty.RegisterAttached("BoundPassword", typeof(string), typeof(PasswordBoxAssistant), new PropertyMetadata(string.Empty, OnBoundPasswordChanged));
public static readonly DependencyProperty BindPassword = DependencyProperty.RegisterAttached(
"BindPassword", typeof (bool), typeof (PasswordBoxAssistant), new PropertyMetadata(false, OnBindPasswordChanged));
private static readonly DependencyProperty UpdatingPassword =
DependencyProperty.RegisterAttached("UpdatingPassword", typeof(bool), typeof(PasswordBoxAssistant), new PropertyMetadata(false));
private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PasswordBox box = d as PasswordBox;
// only handle this event when the property is attached to a PasswordBox
// and when the BindPassword attached property has been set to true
if (d == null || !GetBindPassword(d))
{
return;
}
// avoid recursive updating by ignoring the box's changed event
box.PasswordChanged -= HandlePasswordChanged;
string newPassword = (string)e.NewValue;
if (!GetUpdatingPassword(box))
{
box.Password = newPassword;
}
box.PasswordChanged += HandlePasswordChanged;
}
private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
// when the BindPassword attached property is set on a PasswordBox,
// start listening to its PasswordChanged event
PasswordBox box = dp as PasswordBox;
if (box == null)
{
return;
}
bool wasBound = (bool)(e.OldValue);
bool needToBind = (bool)(e.NewValue);
if (wasBound)
{
box.PasswordChanged -= HandlePasswordChanged;
}
if (needToBind)
{
box.PasswordChanged += HandlePasswordChanged;
}
}
private static void HandlePasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox box = sender as PasswordBox;
// set a flag to indicate that we're updating the password
SetUpdatingPassword(box, true);
// push the new password into the BoundPassword property
SetBoundPassword(box, box.Password);
SetUpdatingPassword(box, false);
}
public static void SetBindPassword(DependencyObject dp, bool value)
{
dp.SetValue(BindPassword, value);
}
public static bool GetBindPassword(DependencyObject dp)
{
return (bool)dp.GetValue(BindPassword);
}
public static string GetBoundPassword(DependencyObject dp)
{
return (string)dp.GetValue(BoundPassword);
}
public static void SetBoundPassword(DependencyObject dp, string value)
{
dp.SetValue(BoundPassword, value);
}
private static bool GetUpdatingPassword(DependencyObject dp)
{
return (bool)dp.GetValue(UpdatingPassword);
}
private static void SetUpdatingPassword(DependencyObject dp, bool value)
{
dp.SetValue(UpdatingPassword, value);
}
}
}
ViewModel
using System;
namespace WpfEvents._32802407
{
public class ViewModelATM
{
string _pin = "";
public string PIN { get { return _pin; } set { _pin = value; } }
}
}
Related
I have created a program that changes the text in a Label on the first window when you type in a TextBox on the second window. The idea is that I want to open a new window from my first window then on this new I want to type in a name and press a button to have that name be displayed on the first window. Am I making myself understood? Can anyone help me out? Thanks in advance!
The First Window(MainWindow)--
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
var createPlayerWindow = new CreatePlayerWindow();
DataContext = this;
createPlayerWindow.Show();
}
}
Xaml for MainWindow--
<Grid>
<Label x:Name="label2" Content="Name:" HorizontalAlignment="Left" Margin="10,63,0,0" VerticalAlignment="Top"/>
<Label x:Name="lblName" Content="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Margin="90,63,0,0" VerticalAlignment="Top"/>
<Button x:Name="button" Content="Create Player" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="81" Click="button_Click"/>
</Grid>
The Second Window(CreatePlayerWindow)--
public partial class CreatePlayerWindow : Window
{
public CreatePlayerWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
string playerName = txbName.Text;
Player player = new Player(playerName);
this.DataContext = player;
}
}
Xaml for CreatePlayerWindow--
<Grid>
<Label x:Name="label" Content="Name :" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<TextBox x:Name="txbName" HorizontalAlignment="Left" Height="23" Margin="63,12,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="120"/>
<Button x:Name="button" Content="Create Player" HorizontalAlignment="Left" Margin="101,129,0,0" VerticalAlignment="Top" Width="88" Click="button_Click"/>
<Label x:Name="label1" Content="Class :" HorizontalAlignment="Left" Margin="10,41,0,0" VerticalAlignment="Top"/>
<ComboBox x:Name="cmbClass" HorizontalAlignment="Left" Margin="63,45,0,0" VerticalAlignment="Top" Width="120"/>
</Grid>
The Model(Player)--
public class Player : ViewModelBase
{
private string _name;
public string Name
{
get
{
return this._name;
}
set
{
this._name = value;
OnPropertyChanged();
}
}
public Player(string name)
{
_name = name;
}
}
The ViewModel(ViewModelBase)--
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName]string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Have the main page's data context and the window's data context use the same instantiated VM which will have a Name property they both will use.
Then in xamls bind appropriately to the Name property on the VM which will adhere to the INotifyPropertyChange notification so that when it changes it will update the other item.
Don't forget that on the editing XAML the binding Mode should be TwoWay otherwise the user changes will not be seen.
Your MainWindow needs a ViewModel for its databinding. DataContext = this is useless here. So create a new class with a PlayerName property (with backing field), instanciate it in MainWindow constructor and set the main windows databinding to it. In the setter call OnPropertyChanged after setting the backingfield to value. Bind your label to the PlayerName property.
In your click event, you should create a viewmodel(player-)instance of your createplayerwindow and bind it to the createplayerwindow-instance's datacontext. add the missing databindings in your createplayerwindow to update your viewmodel (twoway). Replace the Show() method invocation in your button click event with ShowDialog(). After ShowDialog invokation call the new properties setter with the viewmodel.name value.
Here is the complete Sample code
Using MVVM pattern My requirement is to have a ListView where
If user Taps inside ListView on checkBox Storyboard Animation should play for True False and the ListView binded value should be updated in database. For true the tick should pop up with animation for false the tick should become invisible with animation. Solved as per #Elvis Xia answer
If user taps on ListviewItem Navigate to new page with value
Blueprint
Now I went with Usercontrol creation for the datatemplate. Here I want to identify both events separately user clicking on checkbox or clicking on Item separately. Using ICommand I am creating two Delegates that gets binded to two transparent button which relays tapped event. Dependency of creating transparent buttons and creating delgates while binding them made me think surely there must a better way in which I can utilize MVVM for these events without any code behind.
UserControl XAML
<Button Background="LightBlue" BorderBrush="White" BorderThickness="4" Command="{x:Bind sampleItem.itemTapped}" CommandParameter="{Binding}" HorizontalContentAlignment="Stretch">
<Grid Margin="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Margin="20" HorizontalAlignment="Center" Text="{x:Bind sampleItem.sampleText}" FontSize="30"/>
<Image Grid.Column="1" Height="60" Width="60" Source="ms-appx:///Assets/check_off.png" HorizontalAlignment="Right"/>
<Image x:Name="image" Grid.Column="1" Height="60" Width="60" Source="ms-appx:///Assets/check_on.png" HorizontalAlignment="Right" Visibility="Collapsed" RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<CompositeTransform/>
</Image.RenderTransform>
</Image>
<Button x:Name="btnFav" Grid.Column="1" Height="60" Width="60" HorizontalAlignment="Right" Background="Transparent" Command="{x:Bind sampleItem.favTapped}" CommandParameter="{Binding}">
<Interactivity:Interaction.Behaviors>
<!--<Core:EventTriggerBehavior EventName="Tapped">
<Core:InvokeCommandAction Command="{Binding favTapped}" />
</Core:EventTriggerBehavior>-->
<Core:DataTriggerBehavior Binding="{Binding isFav}" Value="true">
<Media:ControlStoryboardAction Storyboard="{StaticResource StoryboardCheckOn}"/>
</Core:DataTriggerBehavior>
<Core:DataTriggerBehavior Binding="{Binding isFav}" Value="false">
<Media:ControlStoryboardAction Storyboard="{StaticResource StoryboardCheckOff}"/>
</Core:DataTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Button>
</Grid>
</Button>
UserControl XAML codeBehind
MainPageModel sampleItem { get { return this.DataContext as MainPageModel; } }
public MainPageUserControl()
{
this.InitializeComponent();
this.DataContextChanged += (s, e) => this.Bindings.Update();
}
Viewmodel Code
public async Task GetData()
{
for (int i = 0; i < 10; i++)
{
if (i == 3)
sampleList.Add(new MainPageModel { sampleText = "Selected", isFav = true, favTapped= new DelegateCommand<MainPageModel>(this.OnFavTapped), itemTapped= new DelegateCommand<MainPageModel>(this.OnItemTapped)});
else
sampleList.Add(new MainPageModel { sampleText = "UnSelected"+i.ToString(), isFav = null, favTapped = new DelegateCommand<MainPageModel>(this.OnFavTapped), itemTapped = new DelegateCommand<MainPageModel>(this.OnItemTapped) });
}
}
private void OnFavTapped(MainPageModel arg)
{
if (arg.isFav == null) arg.isFav = true;
else
arg.isFav = !arg.isFav;
}
private void OnItemTapped(MainPageModel arg)
{
System.Diagnostics.Debug.WriteLine("Button Value: "+arg.sampleText);
System.Diagnostics.Debug.WriteLine("Selected Item Value: "+selectedItem.sampleText);
}
MainPage Xaml
<Grid Grid.Row="1">
<ListView ItemsSource="{x:Bind ViewModel.sampleList}" IsItemClickEnabled="True" SelectedItem="{Binding ViewModel.selectedItem,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<userControls:MainPageUserControl/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
There must be a better way to achieve the desired result using code behind.
public class ViewMOdel()
{
public ViewModel()
{
favTapped= new DelegateCommand<MainPageModel>(this.OnFavTapped)
itemTapped= new DelegateCommand<MainPageModel>(this.OnItemTapped)
}
public async Task GetData()
{
for (int i = 0; i < 10; i++)
{
if (i == 3)
sampleList.Add(new MainPageModel { sampleText = "Selected", isFav = true});
else
sampleList.Add(new MainPageModel { sampleText = "UnSelected"+i.ToString(), isFav = null});
}
}
private void OnFavTapped(MainPageModel arg)
{
if (arg.isFav == null) arg.isFav = true;
else
arg.isFav = !arg.isFav;
}
private void OnItemTapped(MainPageModel arg)
{
System.Diagnostics.Debug.WriteLine("Button Value: "+arg.sampleText);
System.Diagnostics.Debug.WriteLine("Selected Item Value: "+selectedItem.sampleText);
}
}
Binding should be like this
<Button x:Name="btnFav" Grid.Column="1" Height="60" Width="60" HorizontalAlignment="Right" Background="Transparent" Command="{Binding ElementName=UserControl, Path=Tag.favTapped}" CommandParameter="{Binding}"/>
Update
<ListView ItemsSource="{x:Bind ViewModel.sampleList}" x:Name="Listview"IsItemClickEnabled="True" SelectedItem="{Binding ViewModel.selectedItem,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<userControls:MainPageUserControl Tag="{Binding DataContext,ElementName=Listview}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button x:Name="btnFav" Grid.Column="1" Height="60" Width="60" HorizontalAlignment="Right" Background="Transparent" Command="{Binding ElementName=UserControl, Path=Tag.favTapped}" CommandParameter="{Binding}"/>
Update2 using EventTriggerBehavior
favTapped = new DelegateCommand<RoutedEventArgs>(this.OnFavTapped);
private void OnFavTapped(RoutedEventArgs arg)
{
var item = (( arg.OriginalSource )as Button).DataContext as MainPageModel
}
<Button n x:Name="btnFav" Grid.Column="1" Height="60" Width="60" HorizontalAlignment="Right" Background="Transparent" >
<interact:Interaction.Behaviors>
<interactcore:EventTriggerBehavior EventName="Click" >
<interactcore:InvokeCommandAction Command="{Binding ElementName=usercontrol, Path=Tag.favTapped}" />
</interactcore:EventTriggerBehavior>
</interact:Interaction.Behaviors>
</Button>
The DataContext of every item in your project is an instance of MainPageModel class. So the favTapped command should be added to MainPageModel class. And it is a command, so favTapped should be an instance of a new class,which implements ICommand interface.
And if you don't want the animation to show at the page's first load, you can set isFav to bool?. And when the page first loads, set the value of isFav to null, thus it won't trigger the animation action.
Below are the Codes snippets and Demo Link:
ViewModelCommands.cs:
public class ViewModelCommands : ICommand
{
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
//if it's a tapped event
if (parameter is TappedRoutedEventArgs)
{
var tappedEvent = (TappedRoutedEventArgs)parameter;
var gridSource = (Grid)tappedEvent.OriginalSource;
var dataContext = (MainPageModel)gridSource.DataContext;
//if tick is true then set to false, or the opposite.
if (dataContext.isFav == null)
{
dataContext.isFav = true;
} else
{
dataContext.isFav = !dataContext.isFav;
}
}
}
}
MainPageModel.cs:
public class MainPageModel:BindableBase
{
public MainPageModel() {
favTapped = new ViewModelCommands();
}
public ViewModelCommands favTapped { get; set; }
private string _sampleText;
public string sampleText
{
get
{
return this._sampleText;
}
set
{
Set(ref _sampleText, value);
}
}
private bool? _isFav;
public bool? isFav
{
get
{
return this._isFav;
}
set
{
Set(ref _isFav, value);
}
}
}
Here is the complete Demo:Demo Project
Update:
When using DelegateCommand, you can add the command Property to MainPageModel.cs and since the DataContext of the items are MainPageModel instances. You can use this.isFav to change the clicked item's value of isFav.
Here are the codes of MainPageModel.cs:
public class MainPageModel : BindableBase
{
private DelegateCommand _favTapped;
public DelegateCommand favTapped
{
get
{
if (_favTapped == null)
{
_favTapped = new DelegateCommand(() =>
{
//Here implements the check on or off logic
this.CheckOnOff();
}, () => true);
}
return _favTapped;
}
set { _favTapped = value; }
}
private void CheckOnOff()
{
if (this.isFav == null)
{
this.isFav = true;
}
else
{
this.isFav = !this.isFav;
}
}
private string _sampleText;
public string sampleText
{
get
{
return this._sampleText;
}
set
{
Set(ref _sampleText, value);
}
}
private bool? _isFav;
public bool? isFav
{
get
{
return this._isFav;
}
set
{
Set(ref _isFav, value);
}
}
}
For Listview item selected
You can use ListView.ItemClick Event. But you should also set IsItemClickEnabled="True",otherwise the event handler won't be fired.
For The subitem of Listview tapped
You can use Tapped Event of userControl.
Here are the Xaml codes, that shows how to register the above two events:
<Grid Grid.Row="1">
<ListView IsItemClickEnabled="True" ItemClick="ListView_ItemClick_1" ItemsSource="{x:Bind ViewModel.sampleList}">
<ListView.ItemTemplate>
<DataTemplate>
<userControls:MainPageUserControl Tapped="MainPageUserControl_Tapped"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
I have below code to bind ListBox data using MVVM. I would like to implment the Command from MVVM, data is binded completely and I don't know why it doesn't work with the Command. I don't receive the message when clicking on the button.
ViewModel
public class BookmarkViewModel : INotifyPropertyChanged
{
public BookmarkViewModel()
{
DataSource ds = new DataSource();
deleteBookmark = new Command(executeCommand) { Enabled = true };
_bk = ds.getBookmarkDetail();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
List<BookmarkDetail> _bk;
public List<BookmarkDetail> Bookmarks
{
get { return _bk; }
set
{
if (_bk != value)
{
_bk = value;
OnPropertyChanged("Bookmarks");
}
}
}
private Command deleteBookmark;
public Command DeleteBookmark
{
get
{
return deleteBookmark;
}
set
{
deleteBookmark = value;
}
}
void executeCommand()
{
System.Windows.MessageBox.Show(_bk[0].SuraName);
}
public class Command : ICommand
{
private readonly Action executeAction;
private bool enabled;
public bool Enabled
{
get
{
return enabled;
}
set
{
if (enabled != value)
{
enabled = value;
if (CanExecuteChanged != null)
CanExecuteChanged(this, new EventArgs());
}
}
}
public Command(Action executeAction)
{
this.executeAction = executeAction;
}
public bool CanExecute(object parameter)
{
return enabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
executeAction();
}
}
}
and XAML binding
<ListBox x:Name="lsbBookmarks" FontFamily="./Fonts/ScheherazadeRegOT.ttf#Scheherazade"
FlowDirection="RightToLeft"
Style="{StaticResource ListBoxStyle1}"
ItemsSource="{Binding Bookmarks}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="60"></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0"
HorizontalAlignment="Stretch">
<TextBlock Padding="20,0,10,0" HorizontalAlignment="Stretch">
<Run FontSize="50" Text="{Binding ArabicText.ArabicAyaNumber}"
FontFamily="./Fonts/KPGQPC.otf#KFGQPC Uthmanic Script HAFS"
Foreground="Blue"/> <Run FontSize="30" Text="{Binding ArabicText.Aya}"/>
</TextBlock>
</StackPanel>
<Button Grid.Column="1" Tag="{Binding ArabicText.ArabicTextID}"
VerticalAlignment="Center"
Height="60" Width="50" HorizontalAlignment="Right"
Content="X" BorderBrush="Red"
Background="Red" BorderThickness="0"
Padding="0" Command="{Binding DeleteBookmark}"></Button>
</Grid>
<Line X1="0" X2="1" Y1="0" Y2="0" Stretch="Fill" VerticalAlignment="Bottom"
StrokeThickness="1" Stroke="LightGray" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Any Ideas, How to implement the Command using MVVM?
Thanks!
If I were you I would:
Move the Command implementation to a separate file or declare it outside the scope of the BookmarkViewModel class.
Use the second option as ig2r suggested. Your binding would look like this:Command="{Binding DataContext.DeleteBookmark, ElementName=lsbBookmarks}" You can also use any other ElementName other than lsbBookmarks that's defined as a parent of the ListBox.
It appears that your DeleteBookmark command is exposed as a property on the BookmarkViewModel class, whereas the actual data context within the DataTemplate used to render individual ListBox items will be an instance of BookmarkDetail. Since BookmarkDetail does not declare a DeleteBookmark command, the binding fails.
To correct this, either:
Define and expose the DeleteBookmark command on the BookmarkDetail class, or
Extend your command binding to tell the binding system where to look for the delete command, e.g., Command="{Binding DataContext.DeleteBookmark, ElementName=lsbBookmarks}" (untested).
When the user wants to add a new Reminder, they click the add button on the mainWindow; and once they have added the data, it should display it in a listbox on the main window using an observable collection.
This brings up a new window which brings up options of, at the moment Date and message.
When the user has entered the data, Finish method is called.
The issue is, when the user has finished inputting the data on the new window, I add it to the reminder collection, but it doesn't update on the main window. I am wondering if is a datacontext issue and if I am even going about this the right way?
Thanks for the help.
Add Window:
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class AddWindow : Window, INotifyPropertyChanged
{
private MainWindow mainW;
public AddWindow(MainWindow mW)
{
InitializeComponent();
mainW = mW;
this.Show();
DataContext = this;
}
private void Finish(object sender, RoutedEventArgs e)
{
mainW.Reminders.Add(new Remind(SelectedDate, Message));
this.Close();
}
private DateTime selectedDate = DateTime.Today;
public DateTime SelectedDate
{
get
{
return selectedDate;
}
set
{
if (value != selectedDate)
{
selectedDate = value;
RaisePropertyChange("SelectedDate");
}
}
}
private string message;
public string Message
{
get
{
return message;
}
set
{
if (message != value)
{
message = value;
RaisePropertyChange("Message");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChange(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
}
Add Xaml
<TextBox Name="Time" HorizontalAlignment="Left" Height="28" Margin="124,60,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="115"/>
<DatePicker SelectedDate="{Binding SelectedDate}" HorizontalAlignment="Left" Height="28" Margin="124,27,0,0" VerticalAlignment="Top" Width="115"/>
<TextBox Text="{Binding Msg}" HorizontalAlignment="Left" Height="58" Margin="123,93,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="144"/>
<Button Content="Finish" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="135,226,0,0" Click="Finish" />
MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private ObservableCollection<Remind> reminders = new ObservableCollection<Remind>();
public ObservableCollection<Remind> Reminders
{
get
{
return reminders;
}
}
private void Add(object sender, RoutedEventArgs e)
{
AddWindow addWindow = new AddWindow(this);
}
}
Mainwindow Xaml:
</MenuItem>
<MenuItem Header="About">
<MenuItem Header="Info"/>
</MenuItem>
</Menu>
<Button Content="New" HorizontalAlignment="Left" Height="26" Margin="6,279,0,0" VerticalAlignment="Top" Width="81" Click="Add" />
<Button Content ="Delete" HorizontalAlignment="Left" Height="26" Margin="87,279,0,0" VerticalAlignment="Top" Width="79" />
<Button Content="Change" HorizontalAlignment="Left" Height="26" Margin="166,279,0,0" VerticalAlignment="Top" Width="73" />
<ScrollViewer Name="Scroller" HorizontalAlignment="Left" Height="235" Margin="0,31,0,0" VerticalAlignment="Top" Width="346">
<ListBox ItemsSource= "{Binding Reminders}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Height="41" Width="293" >
<TextBlock Text="{Binding Path=dateT}"/>
<TextBlock Text="{Binding Path=Msg}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
<Separator HorizontalAlignment="Left" Height="13" Margin="0,266,0,0" VerticalAlignment="Top" Width="362"/>
Remind :
public class Remind : INotifyPropertyChanged
{
public Remind(DateTime dt, string ms)
{
DateT = dt;
Msg = ms;
}
private DateTime datet;
public DateTime DateT
{
get
{
return datet;
}
set
{
if (datet != value)
{
datet = value;
RaisePropertyChange("dateT");
}
}
}
private string msg;
public string Msg
{
get
{
return msg;
}
set
{
if (msg != value)
{
msg = value;
RaisePropertyChange("Msg");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChange(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
Change dateT to DateT in your main window
<TextBlock Text="{Binding Path=DateT}"/>
and you are done.
Under the bottom line everthing with the datacontext was ok. Your the 2 wrong property names were missspelled.
Hm, I created a small solution with your code and it just works fine. The main windows's list gets updated right after I click finish. The only small problem is you use the wrong binding in AddWindow to the message. You bind to "Msg" but it should be "Message" in the 3rd line above:
<TextBox Text="{Binding Message}" HorizontalAlignment="Left" Height="58" Margin="123,93,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="144"/>
Code looks fine but I can see one issue in it:
TextBox in AddWindow is binded with Msg but the corresponding property name in code behind is Message. So, textBox is never binded properly and hence new item is added in collection with String.Empty value for Msg.
<TextBox Text="{Binding Msg}" <-- HERE. It should be Message.
However, it should still show a new object in collection on GUI with empty string and DateTime value set on AddWindow even in case of binding failure.
For updated Remind class in question:
One issue in XAML binding where you are binding with field instead of it's wrapper property.
<TextBlock Text="{Binding Path=dateT}"/> <-- HERE, Path name should be DateT.
I've looked through multiple solutions on here and the internet on how to solve this but I can't seem to get it. I'm just trying to make a simple login for an application I'm developing. Eventually I'll have it connected to an SQL database on a server but forget that right now. Here's the code:
<Controls:MetroWindow x:Class="ScotiaPlayTrade.Wpf.Application.LoginWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ff="clr-namespace:ScotiaPlayTrade.Wpf.Application"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:System="clr-namespace:System;assembly=mscorlib" Title="Authentication"
Height="400" Width="600" WindowStartupLocation="CenterScreen" TitleForeground="#999988"
ResizeMode="NoResize" WindowStyle="None" WindowState="Normal" ShowMaxRestoreButton="False">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<!--Username Layout-->
<TextBlock
Margin="10,10,10,10"
Grid.Column="0"
Grid.Row="0">
User Name
</TextBlock>
<TextBox
Margin="10,10,10,10"
x:Name="userName"
Grid.Column="5"
Grid.Row="0"
Text="{x:Static System:Environment.UserName}"
IsReadOnly="True">
</TextBox>
<!--Password Layout-->
<TextBlock
Margin="10,10,10,10"
Grid.Column="0"
Grid.Row="1">
Password
</TextBlock>
<PasswordBox
Margin="10,10,10,10"
Width="200"
x:Name="PasswordBox"
ff:PasswordBoxAssistant.BindPassword="true" x:PasswordBoxAssistant.BoundPassword="{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Grid.Column="1"
Grid.Row="1">
</PasswordBox >
<!--Buttons Layout-->
<StackPanel Margin="3,3,3,3" Orientation="Horizontal" Grid.Column="1" Grid.Row="2">
<Button
Margin="10,10,10,10"
Click="Login_Click"
IsDefault="True">
Login
</Button>
<Button
Margin="10,10,10,10"
Click="Exit_Click"
IsCancel="True">
Exit
</Button>
<Button
Margin="10,10,10,10"
Click="AddNewUser_Click"
IsCancel="True">
New User
</Button>
</StackPanel>
</Grid>
That's my LoginWindow.xaml and here's my cs code for the window.
public partial class LoginWindow
{
public LoginWindow()
{
InitializeComponent();
}
//Clicking Login Button
private void Login_Click(object sender, RoutedEventArgs e)
{
//Check that password field is not null
if (PasswordBox != null)
{
//Check that username matches password, if it matches then open main window
//Launch the main window after authentication is complete
MainWindow myMainWindow = new MainWindow();
myMainWindow.Show();
//Close the login screen
Close();
}
else
{
MessageBox.Show("Password field cannot be empty!");
}
}
//Clicking Exit Button
private void Exit_Click(object sender, RoutedEventArgs e)
{
Close();
}
//Clicking Exit Button
private void AddNewUser_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Not yet implemented");
}
}
I've tried everything with passwordbox it keeps giving me an error that WPF doesn't support it, things like PasswordBoxAssistant, and Helper. Would appreciate any help. Here are the error messages:
Error 1 PasswordBoxAssistant is not supported in a Windows Presentation Foundation (WPF) project.
Error 5 The property 'PasswordBoxAssistant.BindPassword' does not exist in XML namespace 'http://schemas.microsoft.com/winfx/2006/xaml'. Line 50 Position 13.
Ok so now I made a class called PasswordValidation and added the PasswordBoxAssistant code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace ScotiaPlayTrade.Wpf.Application
{
public static class PasswordBoxAssistant
{
public static readonly DependencyProperty BoundPassword =
DependencyProperty.RegisterAttached("BoundPassword", typeof(string), typeof(PasswordBoxAssistant), new PropertyMetadata(string.Empty, OnBoundPasswordChanged));
public static readonly DependencyProperty BindPassword = DependencyProperty.RegisterAttached(
"BindPassword", typeof(bool), typeof(PasswordBoxAssistant), new PropertyMetadata(false, OnBindPasswordChanged));
private static readonly DependencyProperty UpdatingPassword =
DependencyProperty.RegisterAttached("UpdatingPassword", typeof(bool), typeof(PasswordBoxAssistant), new PropertyMetadata(false));
private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PasswordBox box = d as PasswordBox;
// only handle this event when the property is attached to a PasswordBox
// and when the BindPassword attached property has been set to true
if (d == null || !GetBindPassword(d))
{
return;
}
// avoid recursive updating by ignoring the box's changed event
box.PasswordChanged -= HandlePasswordChanged;
string newPassword = (string)e.NewValue;
if (!GetUpdatingPassword(box))
{
box.Password = newPassword;
}
box.PasswordChanged += HandlePasswordChanged;
}
private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
// when the BindPassword attached property is set on a PasswordBox,
// start listening to its PasswordChanged event
PasswordBox box = dp as PasswordBox;
if (box == null)
{
return;
}
bool wasBound = (bool)(e.OldValue);
bool needToBind = (bool)(e.NewValue);
if (wasBound)
{
box.PasswordChanged -= HandlePasswordChanged;
}
if (needToBind)
{
box.PasswordChanged += HandlePasswordChanged;
}
}
private static void HandlePasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox box = sender as PasswordBox;
// set a flag to indicate that we're updating the password
SetUpdatingPassword(box, true);
// push the new password into the BoundPassword property
SetBoundPassword(box, box.Password);
SetUpdatingPassword(box, false);
}
public static void SetBindPassword(DependencyObject dp, bool value)
{
dp.SetValue(BindPassword, value);
}
public static bool GetBindPassword(DependencyObject dp)
{
return (bool)dp.GetValue(BindPassword);
}
public static string GetBoundPassword(DependencyObject dp)
{
return (string)dp.GetValue(BoundPassword);
}
public static void SetBoundPassword(DependencyObject dp, string value)
{
dp.SetValue(BoundPassword, value);
}
private static bool GetUpdatingPassword(DependencyObject dp)
{
return (bool)dp.GetValue(UpdatingPassword);
}
private static void SetUpdatingPassword(DependencyObject dp, bool value)
{
dp.SetValue(UpdatingPassword, value);
}
}
}
The following are the errors I get:
Error 1 The name "PasswordBoxAssistant" does not exist in the namespace "clr-namespace:ScotiaPlayTrade.Wpf.Application". 50 13
Error 2 PasswordBoxAssistant is not supported in a Windows Presentation Foundation (WPF) project. 50 58
Error 3 The attachable property 'BindPassword' was not found in type 'PasswordBoxAssistant'. 50 13