I'm very new to .Net and WPF and have a problem. The code is a snippet. I have TextBoxes to enter dates. I check on correct input using GotFocus and LostFocus events.
<TextBox Name="sdDay" Width="40" Text="Day" GotFocus="DateDay_GotFocus" LostFocus="DateDay_LostFocus" Padding="5,5,5,5" HorizontalContentAlignment="Center" Focusable="True"/>
<TextBox Name="sdMonth" Width="50" Text="Month" GotFocus="DateMonth_GotFocus" LostFocus="DateMonth_LostFocus" Padding="5,5,5,5" Margin="5,0,0,0" HorizontalContentAlignment="Center" Focusable="True"/>
<TextBox Name="sdYear" Width="50" Text="Year" GotFocus="DateYear_GotFocus" LostFocus="DateYear_LostFocus" Padding="5,5,5,5" Margin="5,0,0,0" HorizontalContentAlignment="Center" Focusable="True"/>
And the code:
private void DateDay_GotFocus(object sender, RoutedEventArgs e)
{
if (((TextBox)sender).Text == "Day")
((TextBox)sender).Text = string.Empty;
}
private void DateDay_LostFocus(object sender, RoutedEventArgs e)
{
if (((TextBox)sender).Text == string.Empty)
((TextBox)sender).Text = "Day";
else
CheckForCorrectDateDay((TextBox)sender);
}
private void CheckForCorrectDateDay(TextBox b)
{
int day = 0;
try
{
day = int.Parse(b.Text);
if (day < 0 || day > 31)
{
MessageBox.Show("Please enter a correct day.");
b.Text = string.Empty;
b.Focus();
}
}
catch (FormatException)
{
MessageBox.Show("Please enter a number.", "Incorrect Input", MessageBoxButton.OK, MessageBoxImage.Warning);
b.Text = string.Empty;
b.Focus();
}
catch (Exception)
{
throw;
}
}
Now what I want it to do is check for correct input, and if that fails, set the focus back to whatever TextBox had an incorrect entry.
It doesn't work though. After I enter a number outside the range (or letter), the MessageBox will show but the focus shifts to the next TextBox which is for entering the month.
What am I doing wrong?
Your technique for validation here is, to be frank, very poor. That said, I believe the problem is just that WPF is handling the tab after you've set focus, so it is setting focus back to the next item in the focus order.
A simple workaround would be to dispatch a separate message that is processed after the current message:
if (day < 0 || day > 31)
{
MessageBox.Show("Please enter a correct day.");
b.Text = string.Empty;
Dispatcher.BeginInvoke((ThreadStart)delegate
{
b.Focus();
});
}
Doing this ensures that WPF completely processes the LostFocus event handler before it processes the separate message to set focus on the erroneous control.
In terms of how you could tackle this problem in a much nicer way, you could:
Define a view model with properties for Day, Month, and Year (prerequisite: read up on the MVVM pattern)
Implement IDataErrorInfo on the view model
Bind the TextBoxes in the UI to the corresponding properties on the view model (prerequisite: read up on WPF data binding)
Related
I am simply trying to move to a new line when Return + Shift are pressed.
I got this much from a previous post on here:
<TextBox.InputBindings>
<KeyBinding Key="Return" Modifiers="Shift" Command=" " />
</TextBox.InputBindings>
But I cannot find anywhere that explains how to accomplish the move to a new line within the textbox.
I cannot use the: AcceptsReturn="True" as I want return to trigger a button.
If you don't have an ICommand defined, you can attach a handler for the UIElement.PreviewKeyUp event to the TextBox. Otherwise you will have to define an ICommand implementation and assign it to the KeyBinding.Command so that the KeyBinding can actually execute. Either solution finally executes the same logic to add the linebreak.
You then use the TextBox.AppendText method to append a linebreak and the TextBox.CaretIndex property to move the caret to the end of the new line.
MainWindow.xaml
<Window>
<TextBox PreviewKeyUp="TextBox_PreviewKeyUp" />
</Window>
MainWindow.xaml.cs
partial class MainWindow : Window
{
private void TextBox_PreviewKeyUp(object sender, KeyEventArgs e)
{
if (!e.Key.Equals(Key.Enter)
|| !e.KeyboardDevice.Modifiers.HasFlag(ModifierKeys.Shift))
{
return;
}
var textBox = sender as TextBox;
textBox.AppendText(Environment.NewLine);
textBox.CaretIndex = textBox.Text.Length;
}
}
I found a nice way to do it without using an ICommand.
Simply added this PreviewKeyDown event onto the control in xaml:
PreviewKeyDown="MessageText_PreviewKeyDown"
And this is the C# behind:
private void MessageText_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
// Get the textbox
var textbox = sender as TextBox;
// Check if we have pressed enter
if (e.Key == Key.Enter && Keyboard.Modifiers.HasFlag(ModifierKeys.Shift))
{
// Add a new line where cursor is
var index = textbox.CaretIndex;
// Insert a new line
textbox.Text = textbox.Text.Insert(index, Environment.NewLine);
// Shift the caret forward to the newline
textbox.CaretIndex = index + Environment.NewLine.Length;
// Mark this key as handled by us
e.Handled = true;
}
}
I have a combo box which is loaded through a Datasource(Datatable). On a scenario I want the combo box to load with the desired value which I would be passing as combobox1.SelectedValue = custId (Say it's a customer details). custID is set as SelectedValuePath in XAML. When I set that, I am getting a null exception. Anything I am missing?
My XAML:
<ComboBox Height="23" HorizontalAlignment="Left" Margin="332,42,0,0" Name="cmbCustomerName" VerticalAlignment="Top" Width="240" IsEditable="True" DisplayMemberPath="customername" SelectedValuePath="custid" ItemsPanel="{StaticResource cust}" SelectionChanged="cmbCustomerName_SelectionChanged" AllowDrop="True" FontWeight="Normal" Text="--Select a Customer Name--" IsSynchronizedWithCurrentItem="True" />
UPDATE:
C# code:
public customer(int custid)
{
InitializeComponent();
cmbcustomer.SelectedValue= custid.ToString();
}
private void cmbCustomerName_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
cmbcustid.SelectedValue= cmbcustomer.SelectedValue;
}
The selectedValue would not work in this scenario,you should use selectedItem instead,for example:
combobox1.SelectedItem = "custId";
or
combobox1.Text = "custId";
or
combobox1.SelectedIndex = combobox1.Items.IndexOf("custId");
or
combobox1.SelectedIndex = combobox1.FindStringExact("custId")
I will suggest you a method
This is hackish. This is bad coding format.
// Remove the handler
cmbcustomer.SelectionChanged -= cmbCustomerName_SelectionChanged;
// Make a selection...
cmbcustomer.SelectedIndex = combobox1.Items.IndexOf(custid.ToString()); //<-- do not want to raise
// SelectionChanged event programmatically here
// Add the handler again.
cmbcustomer.SelectionChanged += cmbCustomerName_SelectionChanged;
As a temporary solution you can try this out
Wait to access any properties of the ComboBoxes until the they have been loaded:
private void cmbCustomerName_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(cmbcustid != null && cmbcustid.IsLoaded && cmbcustomer != null && cmbcustomer.IsLoaded)
cmbcustid.SelectedValue = cmbcustomer.SelectedValue;
}
I got the issue resolved by assigning the value in the combox_loaded event, as the combobox was just initialized in the constructor, but it was not loaded from the datasource.
Hi I have a Datagrid that is bound to an ObservableCollection of custom AutoCAD layer objects. 3 of the columns are DataGridTextColumns and work correctly. However I also have a DataGridTemplateColumn that contains a StackPanel containing a label and a Rectangle. I am using the label to display the ACI or RGB value of the layer depending how it is set and displaying the colour in the rectangle. The rectangle has a mouse down event that launches a colour picker dialog so the user can select a new colour for the layer. This functionality works. What doesn't work is that the contents of the cell (the label and rectangle) are only shown in a row that is selected and the cell clicked on whereas they need to be visible at all times.
I have tried using a Grid inside the DataTemplate and using the Grid's FocusManager.Focused element to give the Rectangle Focus but this hasn't changed the behaviour.
<t:DataGrid x:Name="layersGrid" ItemsSource="{Binding Layers}"
SelectedItem="{Binding SelectedLayer, Mode=TwoWay}" SelectionMode="Single">
<t:DataGridTemplateColumn Visibility="Visible">
<t:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Grid FocusManager.FocusedElement="{Binding ElementName=swatch}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Colour.ColourProperty}"/>
<Rectangle Name="swatch" Fill="{Binding Colour, Converter={StaticResource colourConverter}}"
MouseLeftButtonDown="swatch_MouseLeftButtonDown"/>
</StackPanel>
</Grid>
</DataTemplate>
</t:DataGridTemplateColumn.CellEditingTemplate>
</t:DataGridTemplateColumn>
</t:DataGrid.Columns>
</t:DataGrid>
Additionally, once you change the colour of the layer in the model view, the rectangle hasn't updated until another row is selected and then the changed one is selected again.
private void swatch_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Colour col = LaunchColourPickerCode();
((LayersModel)this.Resources[MODEL]).SelectedLayer.Colour = col;
}
The problem with them not displaying has been fixed by using CellTemplate instead of a CellEditingTemplate
I adapted surfen's answer on this page to solve the selection problem
How to perform Single click checkbox selection in WPF DataGrid?
Replacing his method with this:
private static void GridColumnFastEdit(DataGridCell cell, RoutedEventArgs e)
{
if (cell == null || cell.IsEditing || cell.IsReadOnly)
return;
DataGrid dataGrid = FindVisualParent<DataGrid>(cell);
if (dataGrid == null)
return;
if (!cell.IsFocused)
{
cell.Focus();
}
DataGridRow row = FindVisualParent<DataGridRow>(cell);
if (row != null && !row.IsSelected)
{
row.IsSelected = true;
}
}
and adding an event on the swatch to obtain the cell it is in
private void swatch_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DataGridCell cell = null;
while (cell == null)
{
cell = sender as DataGridCell;
if (((FrameworkElement)sender).Parent != null)
sender = ((FrameworkElement)sender).Parent;
else
sender = ((FrameworkElement)sender).TemplatedParent;
}
GridColumnFastEdit(cell, e);
}
Also thanks to kmatyaszek
Quite a few posts around this area, but none are helping me... here's the scenario: I've got two "season" drop downs to simulate a range. If you pick a season in the begin range one, the viewmodele automatically sets the property bound to the end range to the same season (so it defaults to a single year and not a range. Here's what the XAML looks like (removed lot of the formatting attibutes for readability):
<ComboBox ItemsSource="{Binding AvailableSeasons, Mode=OneWay}"
SelectedItem="{Binding SelectedBeginRangeSeason, Mode=TwoWay}"
ItemTemplate="{DynamicResource SeasonItemShortFormat}" />
<ComboBox ItemsSource="{Binding AvailableSeasons, Mode=OneWay}"
SelectedItem="{Binding SelectedEndRangeSeason, Mode=TwoWay}"
ItemTemplate="{DynamicResource SeasonItemShortFormat}" />
The properties in the view model look like this:
private Season _selectedBeginRangeSeason;
private const string SelectedBeginRangeSeasonPropertyName = "SelectedBeginRangeSeason";
public Season SelectedBeginRangeSeason {
get { return _selectedBeginRangeSeason; }
set {
if (_selectedBeginRangeSeason != value) {
var oldValue = _selectedBeginRangeSeason;
_selectedBeginRangeSeason = value;
RaisePropertyChanged<Season>(SelectedBeginRangeSeasonPropertyName, oldValue, value, true);
}
}
}
private Season _selectedEndRangeSeason;
private const string SelectedEndRangeSeasonPropertyName = "SelectedEndRangeSeason";
public Season SelectedEndRangeSeason {
get { return _selectedEndRangeSeason; }
set {
if (_selectedEndRangeSeason != value) {
Debug.WriteLine("Updating property SelectedEndRangeSeason...");
var oldValue = _selectedEndRangeSeason;
_selectedEndRangeSeason = value;
Debug.WriteLine("Broadcasting PropertyChanged event for property SelectedEndRangeSeason...");
RaisePropertyChanged<Season>(SelectedEndRangeSeasonPropertyName, oldValue, value, true);
}
}
}
private void UpdateSelectedSeasonSelectors() {
// if the end range isn't selected...
if (_selectedEndRangeSeason == null) {
// automatically select the begin for the end range
SelectedEndRangeSeason = _selectedBeginRangeSeason;
}
}
I've verified the end property is being changed both with the debug statements and with unit tests, but the UI isn't changing when I select it... can't figure out what's going on and have looked at this so many different ways...
Did you get the SelectedSeason from the AvailableSeasons collection? If not, did you implement anything special to compare Seasons?
For example, suppose you have
<ComboBox ItemsSource="{Binding AvailableSeasons}"
SelectedItem="{Binding SelectedSeason}" />
If SelectedSeason = new Season(); the SelectedItem binding won't work because new Season(); does not exist in AvailableSeasons.
You'll need to set SelectedSeason = AvailableSeasons[x] for SelectedItem to work because that makes the two items exactly the same. Or you can implement some custom method to compare the two seasons to see if they're the same. Usually I just overwrite the ToString() method of the class being compared.
Try to fire an event from the ViewModel to notify the UI to refresh the calendar.
I want to add a new line in my datagrid when I press 'TAB' key on last cell of the datagrid.
I am using MVVM pattern to do this. I have came with a solution, I assinged Tab key to the Input binding of the datagrid:
<DataGrid.InputBindings>
<KeyBinding Command="{Binding Path=InsertNewLineCommand}" Key="Tab"></KeyBinding>
</DataGrid.InputBindings>
And added following code to InsertNewLineCommand:
private void ExecuteInsertNewLineCommand()
{
//Checked is SelectedCell[0] at last cell of the datagrid
{
InsertNewLine();
}
}
But the problem is ON ADDING KEYBINDING='TAB' MY NORMAL TAB FEATURE ON THE GRID DISABLES (MOVING TO NEXT CELL AND SO...)
Just determine if you are on the last column then execute your command.
I am using PreviewKeyDown, so I could test the logic, but you can put that in your executeCommand method. Anyway, this should get you started:
<DataGrid PreviewKeyDown="DataGrid_PreviewKeyDown" SelectionUnit="Cell" ....
private void DataGrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (!Keyboard.IsKeyDown(Key.Tab)) return;
var dataGrid = (DataGrid) sender;
var current = dataGrid.Columns.IndexOf(dataGrid.CurrentColumn);
var last = dataGrid.Columns.Count - 1;
if (current == last)
ExecuteInsertNewLineCommand();
}