I created a pretty straight forward desktop application with WPF and net6.0. The application contains a DataGrid with the following XAML code:
<DataGrid x:Name="tblTopics"
CanUserAddRows="True"
AddingNewItem="tblTopics_AddingNewItem"
/>
The binding was done during the initalization of the application with the following statement:
tblTopics.ItemsSource = Topics;
Where Topics is an ObservableList containing the class Topic. Everything works as intended and all rows are properly loaded in the application. Also, if I edit some rows, the changes are correctly transfered to the ItemSource.
However, if a new item is added via clicking in the empty row at the bottom, this change or rather addition is not reflected in the ItemSource. My code for AddingNewItem looks as follows:
private void tblTopics_AddingNewItem(object sender, AddingNewItemEventArgs e)
{
Debug.WriteLine("New Topic added...");
Topic newTopic = new Topic(TopicType.NO_TYPE, "Title", "Description", DateTime.Now, "Responsible");
e.NewItem = newTopic;
}
Interestingly, if I add the item "manually" via code upon a click on a button to the ItemSource, the item will be displayed in the application as well. No problem there. But what am I doing wrong with the above approach? Why is the newly added item not being transferred to the ItemSource?
By accident I found the solution to my problem, thx to WPF DataGrid - Event for New Rows?
The issue is that "Objects are persisted (inserted or updated) when the user leaves a row that he was editing. Moving to another cell in the same row updates the corresponding property through data binding, but doesn't signal the Model (or the Data Access Layer) yet. The only useful event is DataGrid.RowEditEnding. This is fired just before committing the modified row."
So accordingly, upon the RowEditEnding event, I check whether the user committed a change and if so, I add the rowItem to the itemsource.
private void tblTopics_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
{
if (e.EditAction == DataGridEditAction.Commit)
{
var newItem = e.Row.DataContext as ItemSourceDataType;
if (!MyItemSource.Contains(newItem)){
//add newItem to itemsource
}
}
}
}
Related
I have a TextBox inside a ListView. I dynamically add ListView items on KeyDown event (on adding the new item to my observable collection a new ListView Item is created due to Two-Way Binding).
Now when a new element is added I want to set focus to the TextBox of newly created ListView item. It is a bit complex than I thought, Help me solve this problem.
I originally thought it could be possible to use ListView.ContainerFromItem to retrieve the newly added item and then use VisualTreeHelper to search for the TextBox within the template and focus it. It turns out however, that this solution does not work. After adding the item, the container for the list item is not materialized immediately (which is logical, as the control just got the notification about the collection change, but didn't have any time to build up the control hierarchy yet, as our code is still executing).
In fact, the problem has a far simpler solution. You can use the Loaded event on the TextBox within the template. This will be called only once, when the template is first materialized. This is not a perfect solution however, see update below.
In my example I have the following template:
<DataTemplate>
<Grid>
<TextBox Loaded="InputTextBox_Loaded" />
</Grid>
</DataTemplate>
And in the code-behind:
private void InputTextBox_Loaded(object sender, RoutedEventArgs e)
{
var textBox = (TextBox)sender;
textBox.Focus(FocusState.Programmatic);
}
Update: Virtualization
Turns out there is a catch - virtualization creates a number of copies of the template in memory (depending on the window size) to allow for comfortable scrolling, but after that it will just reuse the existing controls - and the Loaded event will never be called again - that's a problem. Luckily, we can solve this as well - instead of the Loaded event we will use DataContextChanged.
Updated XAML:
<DataTemplate>
<Grid >
<TextBox DataContextChanged="TextBox_DataContextChanged" />
</Grid>
</DataTemplate>
Updated code-behind:
private void TextBox_DataContextChanged(
FrameworkElement sender,
DataContextChangedEventArgs args)
{
var textBox = (TextBox)sender;
if ( args.NewValue == Items.Last())
{
//last item, focus it
textBox.Focus(FocusState.Programmatic);
}
}
Ok, that is better, we are getting there! Only one thing left to make it perfect. The current configuration means that once we scroll the last item into view, it will always get focus, which might not be what we want. Instead, we probably want this to happen only once - when the item is newly added. We can do so by adding a bool flag which we set to true when adding a new item into the collection and flip back to false when we focus it the first time:
//set this to true when a new item is being added to the collection
private bool _focusItem = true;
private void TextBox_DataContextChanged(
FrameworkElement sender,
DataContextChangedEventArgs args)
{
var textBox = (TextBox)sender;
if (args.NewValue == Items[Items.Count - 1] && _focusItem)
{
//last item, focus it
textBox.Focus(FocusState.Programmatic);
_focusItem = false;
}
}
I have a ComboBox control in my UWP application and I need to save the selected index of it when the user is selecting an option! Then, I need to save this index in local settings and when the user is coming back on this page, I want the ComboBox to have this saved index as the selected index. I need this functionality in my settings page! Can anyone help me?
This is my code:
private void fuelTypeSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var localSettings = ApplicationData.Current.LocalSettings;
try
{
if(localSettings.Values.ContainsKey("selectedIndex"))
{
int index = (int)localSettings.Values["selectedIndex"];
fuelTypeSelector.SelectedIndex = index;
//update the saved index
if(fuelTypeSelector.SelectedIndex!=index)
{
localSettings.Values["selectedIndex"] =
fuelTypeSelector.SelectedIndex;
}
}
else
{
// index does not exist
localSettings.Values.Add("selectedIndex",
fuelTypeSelector.SelectedIndex);
}
}
catch(Exception ex)
{
}
}
To get the selected item of ComboBox, you can handle its SelectionChanged event, just for example here:
<ComboBox SelectionChanged="ComboBox_SelectionChanged">
<ComboBoxItem>Item 1</ComboBoxItem>
<ComboBoxItem>Item 2</ComboBoxItem>
<ComboBoxItem>Item 3</ComboBoxItem>
<ComboBoxItem>Item 4</ComboBoxItem>
<ComboBoxItem>Item 5</ComboBoxItem>
</ComboBox>
code behind:
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//you can get the selected item like this:
var combo = sender as ComboBox;
var selecteditem = combo.SelectedItem;
//or, since ComboBox DOESN'T support multiple selection, you can get the item like:
var selecteditems = e.AddedItems.FirstOrDefault();
}
Or if you just need the index of this item, you can use the first method and change the code like this: var selectedindex = combo.SelectedIndex;. And of course we can also add items to the Collection of ComboBox through data binding.
By saving the selected item, I personal think it's better to save your local settings when your app is at the suspending stage, and read the settings data at the launched stage. To check the lifecycle of UWP app, the official document Launching, resuming, and background tasks will help you. This means you will have to save your page state during the run time of your app, to do this, you can cache your Setting page, for more information about page state, you can refer to my answer in UWP page state manage.
For saving and retrieving settings part, here is the official documentation: Store and retrieve settings and other app data, there are some sample code in this doc.
Finally since you're new to UWP app development, you can refer to How-to articles for UWP apps on Windows 10 to get started. And there are a lot of official UWP samples on GitHub which may also help.
I have a ContextMenuStrip for a right-click context menu. Inside is a ToolStripMenuItem that contains a ToolStripItem array. Inside the array is a dynamically created ToolStripComboBox.
The issue I am having is that I cannot access the meat of the combobox. It has been linking to a databinding through the ComboBox. A general version of the code is below.
myToolStripComboBox.ComboBox.DataSource = enumList;
ToolStripItem[] toolStripItems = new ToolStripItem[1];
toolStripItems[0] = myToolStripComboBox;
ToolStripMenuItem sortOrder = new ToolStripMenuItem("Sorter", null, toolStripItems);
rightClickPopupMenu.Items.Add(sortOrder);
Looking through the debug shows that the datasource is indeed still set and I have all the required values, but the right-click context menu refuses to show anything but a blank combobox.
Try this :
private void chData_MouseMove(object sender, MouseEventArgs e)
{
try
{
//your combobox binding Code
tooltip.SetToolTip(cmdfoo, tipInfo);
}
catch { }
}
This is just a simple example.
Had the local c# wizard at work look at it. Something along the lines of the databinding not being called by a proper parent object.
The solution was to just directly add the times to the combo box items.
dropDown.ComboBox.Items.Add(enum);
So what I have done is make a list of the enum objects and done a foreach to add them all.
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 have a user interface with a TabControl that initially displays a start page. Other items can be added to it by double-clicking on content in, for example, a DataGrid. New tabs should be selected when they are created. If the document corresponding to the item in the grid is already open, then the existing tab for that document should be opened rather than creating a new one.
I know that I should be able to programmatically select a tab by setting the TabControl's SelectedItem or SelectedIndex properties. However, the desired tab never actually activates. If I set one and then inspect the TabControl's state in the debugger, then both fields seem to update properly. However, after I continue execution, I see that the selected tab remains unchanged in the UI, and if I pause and inspect the TabControl's state again I see that the SelectedItem and SelectedIndex have returned to their previous values. Selecting a tab by clicking on it in the UI, on the other hand, works just fine.
Here's the declaration for the TabControl:
<TabControl x:Name="Tabs" >
<TabItem x:Name="StartPageTab" Header="Start Page" DataContext="{Binding Path=StartPageViewModel}">
...
</TabItem>
</TabControl>
And the code for adding and selecting tabs:
private void _SelectTab(MyViewModel model)
{
TabItem tab;
if (_TryFindTab(model, out tab)) Tabs.SelectedItem = tab;
}
private bool _TryFindTab(MyViewModel target, out TabItem tab)
{
foreach (TabItem item in Tabs.Items)
{
MyViewModel model = item.DataContext as MyViewModel;
if (model != null && model.Equals(target))
{
tab = item;
return true;
}
}
tab = null;
return false;
}
private void _AddTab(MyViewModel model)
{
TabItem tab = new TabItem { DataContext = model, Content = new MyView() };
Binding bind = new Binding { Source = model, Path = new PropertyPath("Name") };
tab.SetBinding(TabItem.HeaderProperty, bind);
Tabs.Items.Add(tab);
Tabs.SelectedItem = tab;
}
It turned out to be related to something I conveniently omitted from the original problem description:
The DataGrid in question was in the content for StartPageTab. I was handling double-clicks on that DataGrid by capturing its MouseDoubleClick event, searching the visual tree to find what DataGridRow was double-clicked (if any), and then raising an event that would eventually be captured by the main window, which would respond by calling either _SelectTab or _AddTab, depending on whether the document in question was already open.
At which point, the call stack would unroll and get back to that MouseDoubleClick event handler. In that handler, I forgot to set the MouseButtonEventArgs's Handled property to true. So WPF kept searching for someone else to handle that click event - and the element that it eventually found would respond by asking for focus, which in turn meant that the original tab needed to get focus back.
Adding e.Handled = true; stopped that whole mess in its tracks, so the new tab could stay selected.
You could try using tab.focus()
I have tabs in my application and this is a quick way to make your selected tab visible.
Have you tried binding to TabItem.IsSelected and updating that in you view model?
In an older C# app I had, using page controls, I was able to force the page active by telling the tab control object to select the tab...
MyTabControlWithMultiplePages.SelectTab(PageIWantShown);