How to programmatically select a TabItem in WPF TabControl - wpf

I would like to know how to select a specific TabItem in a WPF TabControl.
I tried these bellow but nothing work!
MyTabControl.SelectedIndex = x
MyTabControl.SelectedItem = MyTabItem
MyTabControl.SelectedValue = MyTabItem
MyTabItem.IsSelected = True

As #Chris says, any of the first three things should work and as #Phyxx says, it doesn't always really work. The problem is some subtle thing about the order of property changes. To work around it you need to let the WPF invoke your tab-selection code in its own time:
Dispatcher.BeginInvoke((Action)(() => MyTabControl.SelectedIndex = x));
This does just what Phyxx' timer does, but in a slightly less extreme way.

All your examples except the third one are correct and will work. The problem must be at another location. Maybe you reset the item after setting or your code never is called?
Valid
MyTabControl.SelectedIndex = x
MyTabControl.SelectedItem = MyTabItem
MyTabItem.IsSelected = True
Invalid
MyTabControl.SelectedValue = MyTabItem

Loop through the TabItems and for the tab to be selected, set
tabItem.IsSelected = true
If there are any other place due to binding changing you will see problem. Otherwise, the above code should work.

One thing which hasn't been mentioned above:
The main reason something like this won't work is that the tab items do not have the "Name" property set. Each tab item of the tab control which you want to navigate to programmatically must have its name property set for any of the above code to work.
<tabItem Name="tab1"></tabItem>

I have implemented a small MVVM bindings based solution for selecting tab panels pragmatically.
define a property in your view model - Selected int type
bind the property in your view
<TabControl
x:Name="TabsCandidate"
VerticalAlignment="Stretch"
TabStripPlacement="Top"
SelectedIndex="{Binding Selected}"
private int _selected;
public int Selected
{
get { return _selected; }
set
{
_selected = value;
OnPropertyChanged("Selected");
}
}
Set the value to Select property, simply the binding will activate the tab panel.
if you want to navigate from tab panel inside parent tab panels, this solution will simply works, All you need to do is, access the data context of your control and set it
// set the property value of the view model which points the index of the tab controller.
((CandidateViewModel)((System.Windows.FrameworkElement)candidateTab.Content).DataContext).Selected = CandidateLogTabIndex;

Try to set the MyTabControl.SelectedIndex = x in the event handler of DataContextChanged or Loaded of your UI. Hope this will work.

I tried all the methods that should have worked, but like you nothing actually changed the selected tab. In the end I got it to work by putting the tab selection code in a DispatcherTimer tick.
DispatcherTimer switchTabTimer = new DispatcherTimer();
switchTabTimer.Interval = new TimeSpan(0);
switchTabTimer.Tick += (object timerSender, EventArgs timerE) =>
{
myTabControl.SelectedIndex = 0;
switchTabTimer.Stop();
};
switchTabTimer.Start();

if you don't know the index of the tab (hint its not TabIndex) use:
private async Task ChangeTabTo(TabItem wantedTab) {
int index = 0;
for (var i = 0; i < TabControl.Items.Count; i++) {
var tab = TabControl.Items[i];
var t = tab as TabItem;
if (t == null) continue;
if (t == wantedTab) {
index = i;
break;
}
}
await Dispatcher.BeginInvoke((Action)(() => TabControl.SelectedIndex = index));
}
or modify it to search by name if you don't want to keep a reference to the tab

I'm throwing my 2 cents on the topic, since it might help someone out. I'm using WPF with Prims framework.
I was unable to select a tab by binding to SelectedItem or SelectedIndex - it didn't work. I was also unable to set TabItem.Name value from within TabControl.ItemTemplate or TabControl.ContentTemplate.
Instead I implemented event-based solution:
Add Name value for my TabControl.
Create an event - in Prism that means define a class that derives from PubSubEvent<T> (T is the type of parameter - in my case that was the ViewModel object bound to the TabItem>.
Publish that event whenever I want to a tab to be selected.
Subscribe to the event within my View.cs class and set the TabControl.SelectedItem programmatically using FindName.

Related

Simple- How To select an Item from a WPF combo box using coded UI

I have worked with coded UI for web applications but for the first time I am trying to leverage Coded UI for WPF. I want to perform a click on an item from a combobox But, I am unable to achieve the same here in WPF. I tried to loop through the items inside the combo box but It did not work. Tried with Search Property - No result. Also, was trying to figure out the AutomationElement stuff but not able to get a solution. It would be great if I can get an idea of the approach that needs to be followed to achieve the requirement.
I have captured the controls and want to play around with them. No record and playback.
You can use the WpfComboBox's SelectedItem property which takes the name of the item you want to select (as mentioned in my comment in yonder answer)
var myComboBox = this.UIMap.UIMainWindowWindow.UIItemComboBox;
var items = myComboBox.Items;
myComboBox.SelectedItem = items[0].Name;
or you can simply set the SelectedIndex if you already know the index of the item you want to set
var myComboBox = this.UIMap.UIMainWindowWindow.UIItemComboBox;
var items = myComboBox.Items;
myComboBox.SelectedIndex = 0;
or you can first click the combobox to get it expanded and then get the UITestControl for the item element and perform a click on it (unfortunately you have to manually click the combobox because it seems that the ExpandWhileSearching configuration it doesn't work on it)
var myComboBox = this.UIMap.UIMainWindowWindow.UIItemComboBox;
var items = myComboBox.Items;
Mouse.Click(myComboBox);
Mouse.Click(items[0]);
or
var myComboBox = this.UIMap.UIMainWindowWindow.UIItemComboBox;
var items = myComboBox.Items;
myComboBox.Expanded = true;
Mouse.Click(items[0]);
You would create the combobox object the same way you would in Html, just using Microsoft.VisualStudio.TestTools.UITesting.WpfControls namespace. For example:
public WpfComboBox tester
{
get
{
WpfComboBox target = new WpfComboBox();
return target;
}
}
Then, you will create a UITestControlCollection object to store the .Items of the combo box.
UITestControlCollection comboBoxItems = tester.Items;
From here, you should be able to edit and set selected items ( tester.SelectedItem = comboBoxItems[0].ToString();) at will.

How to select first list box item by default?

I'm using ObservableCollection to bind data into list box. Is there a way to make first list item to be selected right after data binding? Is there any event I can use ?
Thank you
Right after (or any point after) setting the datacontext for the listbox (or parent object - probably the page), just set the selected index to the first item in the list.
listbox.SelectedIndex = 0;
If you've got a handler for when the selected index is changed then be sure to ignore when you first set the index.
Create a property named IsSelected in the object contained within the ObservableCollection. Bind this to the ListBoxItem's IsSelected property via a TwoWay binding.
Then, in the page's OnLoaded callback (or wherever you're binding the collection to the ListBox), do something like this
foreach( var obj in myCollection ) {
obj.IsSelected = false;
}
if( myCollection.Count > 0 ) {
myCollection[0].IsSelected = true;
}
// bind the collection to the listbox
why won't you try something like
var listBoxItem = ItemContainerGenerator.ContainerFromItem(myList.First());
listBoxItem.Focus();
or
listBoxItem.IsSelected = true;

this.Teams = new ObservableCollection<TeamViewModel>(_teams);

I'm new to mvvm and I can't seem to find the answer to the following:
I have a datagrid in Silverlight which binds to a observablecollection Teams of a viewmodel:
First I fetch a couple of team objects in a List and then I feed them to my observablecollection
(Public ObservableCollection Teams)
Now I want to add a row in the Datagrid so I created a button in the view and hooked up an Icommand which adds more or less an empty team to my collection:
private void Add()
{
Team _team = new Team();
_team.recid = 1;
_team.teamid = "";
_team.ruleset = "";
_team.name = "";
this.Teams.Add(new TeamViewModel(_team));
}
I think the notifypropertychanged doesn't fire (only when I set the collection), what do I have to do to notify the view of my changed viewmodel?
Thanks in advance,
Michael
Adding to an ObservableCollection does not raise PropertyChanged, but instead raises a collectionchanged event.
The easiest way is to manually RaisePropertyChanged for the Team property at the end of your add function.
If you databound your Teams property (which is of the type ObservableCollection), your Grid should automatically update.
There's no reason to call RaisePropertyChanged("Teams") when adding or removing items from a ObservableCollection.
So in short, it should already work. If it doesn't, you need to post more code.
I guess it didn't show up in the grid because I added empty strings for teamid / name. When I do it like this the row is visible in the grid: _team.recid = 1; _team.teamid = "test"; _team.ruleset = "test"; _team.name = "test"; _team.rowstate = GlobalVariables.Rowstate.added; Thanks Claus / Brandorf for your comments!
Regards, Mike

Combobox background color while not enabled

I have a combobox that I have Enabled = false. When that is the case it causes it to shade to a grey. I was wondering if there was a way I could keep the checkbox background color as cornsilk while it is not Enabled?
The situation is that I have a form that I will refresh with data when an item is selected. If the user selects to edit the record I enable the form to accept changes and since it is mainly textboxes I just change the readonly property of those. But the combobox looks different so I want to see what I can do to make it stay the same like the rest of the form...
Any ideas?
I would simply hide it with a TextBox over it and setting its Visible property to false. Then, you your user click the Edit button, you hide your TextBox and show your ComboBox with its Visible property set to true.
Perhaps you wish to update your TextBox.Text property by setting its value to the ComboBox.SelectedItem property value on the SelectedItemChanged() event handler.
Let's suppose the following:
ComboBox cb = new ComboBox();
// Position, size and other properties are set through design.
cb.SelectedIndex = 0; // Forces selection of first item for demo purposes.
TextBox tb = new TextBox();
tb.Size = cb.Size;
tb.Position = cb.Position;
tb.Text = cb.SelectedItem.ToString();
tb.Visible = true;
tb.Readonly = true;
cb.Visible = false;
Then, clicking the Edit button:
private void EditButton_Click(...) {
tb.Visible = false;
cb.Visible = true;
}
And make your TextBox.Text property value follow your SelectedItem:
private void ComboBox_SelectedIndexChanged(...) {
tb.Text = cb.SelectedItem.ToString;
}
And you would only do the reverse of your EditButton_Click() event handler to bring back your form in read-only mode.
You may consider using Jquery UI or other plugins if aesthetics of form are important. You can control entire look and feel with the CSS.
Hiding combobox with textbox is a possibility as suggested by Will but then you will have to use absolute width for the dropdown.

how to set all row heights of the wpf datagrid when one row height is adjusted

Im using the wpf datagrid and am looking for a way to set the height on all of the rows when the user adjusts one of them. I know the datagrid has a RowHeight property that sets all of the row heights at once, but the how of catching an individual row height changed escapes me
There aren't any events that could be use directly for that. What you could do is use another event that is fired when you resize the rows and other things. The event I'm thinking of right now is PreviewMouseUp, which is release when you release the mouse button anywhere if you datagrid.
What you could do is when the event is fired, you could check the row height of all of your rows and find the one that is different, then update all rows with it.
I arrived on this by trial and error, so long ass you are using an ItemsSource data source it should work fine.
It should work with virtual rows and causes only a brief visual pause and it switches over (this seems mainly down to column autogeneration so can be avoided).
As hacks go it has the advantage of simplicity and the use of mechanics which are not expected to change.
The heuristic on user triggering of the action might be improved but it has not failed on me yet.
using Microsoft.Windows.Controls;
using Microsoft.Windows.Controls.Primitives;
public static class DataGridExtensions
{
public static void LinkRowHeightsToUserChange(this DataGrid dataGrid)
{
double? heightToApply = null;
bool userTriggered = false;
if (dataGrid.RowHeaderStyle == null)
dataGrid.RowHeaderStyle = new Style(typeof(DataGridRowHeader));
if (dataGrid.RowStyle == null)
dataGrid.RowStyle = new Style(typeof(DataGridRow));
dataGrid.RowStyle.Setters.Add(new EventSetter()
{
Event = DataGridRow.SizeChangedEvent,
Handler = new SizeChangedEventHandler((r, sizeArgs) =>
{
if (userTriggered && sizeArgs.HeightChanged)
heightToApply = sizeArgs.NewSize.Height;
})
});
dataGrid.RowHeaderStyle.Setters.Add(new EventSetter()
{
Event = DataGridRowHeader.PreviewMouseDownEvent,
Handler = new MouseButtonEventHandler(
(rh,e) => userTriggered = true)
});
dataGrid.RowHeaderStyle.Setters.Add(new EventSetter()
{
Event = DataGridRowHeader.MouseLeaveEvent,
Handler = new MouseEventHandler((o, mouseArgs) =>
{
if (heightToApply.HasValue)
{
userTriggered = false;
var itemsSource = dataGrid.ItemsSource;
dataGrid.ItemsSource = null;
dataGrid.RowHeight = heightToApply.Value;
dataGrid.ItemsSource = itemsSource;
heightToApply = null;
}
})
});
}
#Aran
do you remember the rationale behind this?
I can tell you: If you remove both lines to unset and reset the items source (which indeed slows the whole process quite a bit), the row you resize will have its height definitively set.
It seems when you resize a row, you change its Height directly, and this overrides any value you set to the dataGrid's RowHeight property for this row in particular. So basically, here is what you can get :
dataGrid's RowHeight = 20
you change the Height of one Row (say the 5th) to 30 => this row's Height is set to 30 and the dataGrid's RowHeight is set to 30. Everything looking good so far.
now, change another row's Height back to 20 (say the 2nd row). you Set this row's Height to 20 and the DataGrid'RowHeight to 20, which puts all the other rows to 20, EXCEPT the 5th row which stays at 30. (because it had been forced to this value before)
emptying the source and resetting it forces each row to be reloaded and take the dataGrid's RowHeight into account, which eliminates the problem.
As far as I know there is no event that is raised when you resize a row's height.
My first suggestion would be to set the RowStyle in order to create a binding (OneWay) between the the DataGridRow's height property and the datagrid's RowHeight property, but
if you check the Row's height after you resize it, it is unchanged, the ActualHeight is the property that contains the row's "actual" height when you resize it, and ActualHeight cannot be set because "it does not have an accessible set accessor".
After trying this I thought: Where does DataGridRow's ActualHeight gets its value from?
I remembered this post that explains how to detect which cell and row got clicked and also shows the DataGrid's default template visual tree.
By trial and error (using the visual tree image in the link above) I found that it was DataGridCellPresenter that stored the Height that was being used (actually I'm not 100% sure about this, it was the first class that had the height changed up the visual tree since DataGridCell).
Apparently DataGrid doesn't expose the API to get the DataGridCellsPresenter from a DataGridRow (as I found out here)
So my first approach was to get all the DataGridCellPresenter in the DataGrid (through the visual tree) after it has been populated and programatically create a binding between the Height property of the DataGridPresenter and the RowHeight property of the DataGrid.
Here's the code to do that (my DataGrid's instance name is dataGrid1):
Getting all the DataGridCellPresenter:
void GetAllDataGridCellPresenters(DependencyObject parent, List<DataGridCellsPresenter> presenters)
{
int numberOfChildren = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < numberOfChildren; i++)
{
if (VisualTreeHelper.GetChild(parent, i) is DataGridCellsPresenter)
{
presenters.Add(VisualTreeHelper.GetChild(parent, i) as DataGridCellsPresenter);
}
else if (VisualTreeHelper.GetChild(parent, i) != null)
{
GetAllDataGridCellPresenters(VisualTreeHelper.GetChild(parent, i), presenters);
}
else
return;
}
}
Setting the bindings programatically on all of them (call this when the Loaded event is raised by the DataGrid):
void SetBindingInDataGridPresenter()
{
List<DataGridCellsPresenter> presenters = new List<DataGridCellsPresenter>();
GetAllDataGridCellPresenters(dataGrid1, presenters);
foreach (DataGridCellsPresenter presenter in presenters)
{
Binding binding = new Binding("RowHeight");
binding.Source = dataGrid1;
binding.Mode = BindingMode.TwoWay;
presenter.SetBinding(DataGridCellsPresenter.HeightProperty, binding);
}
}
(Note: Setting the binding as OneWayToSource didn't work, I really don't know why, I'm probably missing something obvious here...)
This did work... sort of... because I used the Visual Tree to get the DataGridCellsPresenter I only got the visible ones :P, but this shows it can be done this way.
So, finally, the right way to do it would be to supply the DataGrid control template, it can be just as the default one except with the DataGridCellsPresenter's Height property data bound to the RowHeight property of the DataGrid.
I know this does not show exactly how to do it, but you just have to learn (so do I :P)
to redefine a control's template; somehow get the default DataGrid template (or if you're already using another then great, you probably know more than me about it and already know how to do it in order to get the DataGridCellsPresenter Height property automatically bound to the RowHeight DataGrid property) and change it with that bit of magic that gets both height properties bound.

Resources