I have a WPF application with multiple tabs. Under each tab, a user can change some settings (using CheckBoxes, TextBoxes, etc) and then the user must click the "Update" button in order to save those settings. Everything works fine but one of requirements is to alert the user if he tries to switch to other tab without clicking the "Update" button.
So I'm trying to use the
TabItem_LostFocus
event handler to achieve it but this event is triggered every time I click on something within the tab. I guess I can patch this issue by placing
e.Handled = true
for every control I have but this doesn't sound like an elegant solution (especially when I don't have click event handlers for everything under my tabs). Is there some other way to determine when you are switching away from the current tab?
Thank you
To achieve your requirement, you just need to data bind to the TabControl.SelectedIndex or the TabControl.SelectedItem properties:
<TabControl ItemsSource="{Binding TabItemCollection}"
SelectedItem="{Binding SelectedTabItem}" />
Then in your view model or code behind:
private YourDataType selectedItem;
public YourDataType SelectedItem
{
get { return selectedItem; }
set
{
// selectedItem represents the previous TabItem
// value represents the new TabItem
selectedItem = value;
}
}
You can bind to the IsSelected property of each TabItem.. and then do your checking inside the setter
<TabControl>
<TabItem IsSelected="{Binding TabItem1IsSelected}"/>
</TabControl>
Property:
public bool TabItem1IsSelected
{
get { return _tabItem1IsSelected; }
set
{
if (_tabItem1IsSelected)
{
if (!value)
{
// Check to see if user has updated
if (!userUpdated)
{
value = true;
}
}
}
_tabItem1IsSelected = value;
RaisePropertyChanged();
}
}
Related
i have a mvvm application in which i have a DataGrid in Wpf and want to get notified if a user changes a value in a column.
All dataGridColumns have a binding to my viewmodel, which invokes a PropertyChanged Command if it gets changed. Now the Problem is, how i can determine if the property has been changed by the user or by the code? Because i only want to add a note to the corresponding line when it has been changed manually by the user.
The Column of interest is implemented like this in wpf:
<DataGridTextColumn
Header="DIL"
Binding="{Binding DilutionFactor, StringFormat={}{0:n4}}"
Visibility="{Binding Value,
Source={StaticResource DilVis},
Converter={StaticResource BooleanToVisibilityConverter}}"
IsReadOnly="False"/>
Which is bound to the ViewModel Property:
public double DilutionFactor
{
get { return _dilutionFactor; }
set
{
_dilutionFactor = value;
Update(); // PropertyChanged Command
UpdatePipetteVolumes(); // function to update corresponding volumes
}
}
Is there a event or anything i can use to trigger a method when the user changes the value of the DIL column, which is not triggered when the code updates the value?
You could set a boolean flag each time before you programmatically change the value. Then in the property setter you can check that property to see if the user invoked the change. However, this method might need a lot of code changes for heavily used properties.
Another way:
Add a second property which just sets and returns the existing property. Then use that new property for the datagrid binding:
public double DilutionFactorUser
{
get { return this.DilutionFactor; }
set
{
this.DilutionFactor = value;
// Here comes the code that is only executed on user-invoked changes
}
}
public double DilutionFactor
{
get { return _dilutionFactor; }
set
{
_dilutionFactor = value;
Update(); // PropertyChanged Command
UpdatePipetteVolumes(); // function to update corresponding volumes
}
}
Set up your Datagrid to bind to DilutionFactorUser
You could also use the DataGrid.CellEditEnding(object sender, DataGridCellEditEndingEventArgs e) event instead of looking the Property. e.Row.Item will have the data you are binding to.
I have a problem with data binding. I am using MVVM Light.
When I set a breakpoint on the setter of one of the bools in my view model and select the corresponding radio button, the debugger halts and all looks fine.
Then when I continue, select the other radio button and select the first radio button again, the debugger is not halting. What is going wrong?
I have two two radiobuttons:
<RadioButton Margin="5" Grid.Row="1" Content="Browser cookies" GroupName="loginmethod" IsChecked="{Binding IsBrowserCookiesChecked}"/>
<RadioButton Margin="5" Grid.Row="2" Content="Username + password" GroupName="loginmethod" IsChecked="{Binding IsUsernamePasswordChecked}"/>
I have bounded them to two separate bools in my view model which looks as follows:
public const string IsUsernamePasswordCheckedPropertyName = "IsUsernamePasswordChecked";
private bool _isUsernamePasswordChecked = false;
public bool IsUsernamePasswordChecked
{
get
{
return _isUsernamePasswordChecked;
}
set
{
if (_isUsernamePasswordChecked == value)
{
return;
}
RaisePropertyChanging(IsUsernamePasswordCheckedPropertyName);
_isUsernamePasswordChecked = value;
RaisePropertyChanged(IsUsernamePasswordCheckedPropertyName);
}
}
public const string IsBrowserCookiesCheckedPropertyName = "IsBrowserCookiesChecked";
private bool _isBrowserCookiesChecked = true;
public bool IsBrowserCookiesChecked
{
get
{
return _isBrowserCookiesChecked;
}
set
{
if (_isBrowserCookiesChecked == value)
{
return;
}
RaisePropertyChanging(IsBrowserCookiesCheckedPropertyName);
_isBrowserCookiesChecked = value;
RaisePropertyChanged(IsBrowserCookiesCheckedPropertyName);
}
}
I find that when you're doing databinding on radio buttons it's a lot easier to give each button a different GroupName so they don't affect each other, then handle the setting and unsetting of each radio button choice via binding only. For example you could expose just one boolean on your viewmodel, have one radio button bind to that bool, then have the other bind to the same one, but with a inverse boolean converter. Or make the property an enum, then assign each radio button to that property, but with an enum to boolean converter. Then you have the added benefit of fewer properties to worry about on your viewmodel.
When you give radio buttons the same GroupName WPF will try to mess with the other properties every time you change one and you can get some ugly loops and weird behavior.
I'm attempting to create a user control that houses a tab control.
My question is, how do I expose the tab control through the user control so we can add tabs?
The entire control will house three areas: command buttons at the top, the tab control, and a styled textblock that displays messages.
The command buttons will be configurable as dependencyproperties, so we may choose to show the apply button or not when we use the usercontrol. I'm strictly a designer and not a developer, so I'm trying to get my feet wet in building this control, but I'm in a little over my head.
I have no problem whipping up the XAML for what I'm trying to accomplish, just having a hard time making it reusable.
More details on what I have tried so far (haven't tried anything in the answers just yet).
I attempted to add a dependencyproperty that would expose the collection for the tabcontrol itemssource:
public IEnumerable<object> TabSource
{
get { return (IEnumerable<object>)GetValue(TabSourceProperty); }
set { base.SetValue(FunctionPanel.TabSourceProperty, value); }
}
public static DependencyProperty TabSourceProperty = DependencyProperty.Register(
"TabSource",
typeof(IEnumerable<object>),
typeof(FunctionPanel));
And then bind to it in the user control XAML:
<TabControl Grid.Row="1" ItemsSource="{Binding TabSource}" />
Finally, I would like to use it in the window XAML like so:
<local:FunctionPanel>
<local:FunctionPanel.TabSource>
<TabItem Header="Test" />
</local:FunctionPanel.TabSource>
</local:FunctionPanel>
But this returns TabSource is unrecognizable or unaccessible. I will attempt the solutions provided below.
My question is, how do I expose the tab control through the user control so we can add tabs?
One straight forward option would be adding a public method to the user control:
public void AddTab(string header)
{
this.tabControl.Items.Add(header);
}
another option would be to expose the Items property on the user control:
public ItemCollection Items
{
get { return this.tabControl.Items; }
}
I provide you with a sample to open a Page inside a Tab Control , hope it helps :
public void OpenTabForm(Page oPage)
{
try
{
Frame oFrame = new Frame();
oFrame.Content = oPage;
TabItem myItem = new TabItem();
myItem.Header = oPage.Name; //give the header text
myItem.Content = oFrame;
tbtabMain.Items.Add(myItem);
tbtabMain.SelectedItem = myItem;
}
catch (Exception ex)
{
//handle error
}
}
I have a requirement to focus on a specific textbox when a new view is loaded.
The solution was to add this line of code to the OnLoaded event for the view:
Dispatcher.BeginInvoke(() => { NameTextBox.Focus(); });
So this worked for one view, but not another. I spent some time debugging the problem and realized that the new view I was working on had a BusyIndicator that takes focus away from all controls since the BusyIndicator being set to true and false was occuring after the OnLoaded event.
So the solution is to call focus to the NameTextBox after my BusyIndicator has been set to false. My idea was to create a reusable BusyIndicator control that handles this extra work. However, I am having trouble doing this in MVVM.
I started by making a simple extension of the toolkit:BusyIndicator:
public class EnhancedBusyIndicator : BusyIndicator
{
public UserControl ControlToFocusOn { get; set; }
private bool _remoteFocusIsEnabled = false;
public bool RemoteFocusIsEnabled
{
get
{
return _remoteFocusIsEnabled;
}
set
{
if (value == true)
EnableRemoteFocus();
}
}
private void EnableRemoteFocus()
{
if (ControlToFocusOn.IsNotNull())
Dispatcher.BeginInvoke(() => { ControlToFocusOn.Focus(); });
else
throw new InvalidOperationException("ControlToFocusOn has not been set.");
}
I added the control to my XAML file with no problem:
<my:EnhancedBusyIndicator
ControlToFocusOn="{Binding ElementName=NameTextBox}"
RemoteFocusIsEnabled="{Binding IsRemoteFocusEnabled}"
IsBusy="{Binding IsDetailsBusyIndicatorActive}"
...
>
...
<my:myTextBox (this extends TextBox)
x:Name="NameTextBox"
...
/>
...
</my:EnhancedBusyIndicator>
So the idea is when IsRemoteFocusEnabled is set to true in my ViewModel (which I do after I've set IsBusy to false in the ViewModel), focus will be set to NameTextBox. And if it works, others could use the EnhancedBusyIndicator and just bind to a different control and enable the focus appropriately in their own ViewModels, assuming their views have an intial BusyIndicator active.
However, I get this exception when the view is loaded:
Set property 'foo.Controls.EnhancedBusyIndicator.ControlToFocusOn' threw an exception. [Line: 45 Position: 26]
Will this solution I am attempting work? If so, what is wrong with what I have thus far (cannot set the ControlToFocusOn property)?
Update 1
I installed Visual Studio 10 Tools for Silverlight 5 and got a better error message when navigating to the new view. Now I gete this error message:
"System.ArgumentException: Object of type System.Windows.Data.Binding cannot be converted to type System.Windows.Controls.UserControl"
Also, I think I need to change the DataContext for this control. In the code-behind constructor, DataContext is set to my ViewModel. I tried adding a DataContext property to the EnhancedBusyIndicator, but that did not work:
<my:EnhancedBusyIndicator
DataContext="{Binding RelativeSource={RelativeSource Self}}"
ControlToFocusOn="{Binding ElementName=NameTextBox}"
RemoteFocusIsEnabled="{Binding IsRemoteFocusEnabled}"
IsBusy="{Binding IsDetailsBusyIndicatorActive}"
...
>
Update 2
I need to change UserControl to Control since I will be wanting to set focus to TextBox objects (which implement Control). However, this does not solve the issue.
#Matt, not sure
DataContext="{Binding RelativeSource={RelativeSource Self}}"
will work in Silverlight 5, have you tried binding it as a static resource?
Without a BusyIndicator present in the view, the common solution to solve the focus problem is to add the code
Dispatcher.BeginInvoke(() => { ControlToFocusOn.Focus(); });
to the Loaded event of the view. This actually works even with the BusyIndicator present; however, the BusyIndicator immediately takes focus away from the rest of the Silverlight controls. The solution is to invoke the Focus() method of the control after the BusyIndicator is not busy.
I was able to solve it by making a control like this:
public class EnhancedBusyIndicator : BusyIndicator
{
public EnhancedBusyIndicator()
{
Loaded += new RoutedEventHandler(EnhancedBusyIndicator_Loaded);
}
void EnhancedBusyIndicator_Loaded(object sender, RoutedEventArgs e)
{
AllowedToFocus = true;
}
private readonly DependencyProperty AllowedToFocusProperty = DependencyProperty.Register("AllowedToFocus", typeof(bool), typeof(EnhancedBusyIndicator), new PropertyMetadata(true));
public bool AllowedToFocus
{
get { return (bool)GetValue(AllowedToFocusProperty); }
set { SetValue(AllowedToFocusProperty, value); }
}
public readonly DependencyProperty ControlToFocusOnProperty = DependencyProperty.Register("ControlToFocusOn", typeof(Control), typeof(EnhancedBusyIndicator), null);
public Control ControlToFocusOn
{
get { return (Control)GetValue(ControlToFocusOnProperty); }
set { SetValue(ControlToFocusOnProperty, value); }
}
protected override void OnIsBusyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnIsBusyChanged(e);
if (AllowedToFocus && !IsBusy)
{
Dispatcher.BeginInvoke(() => { ControlToFocusOn.Focus(); });
AllowedToFocus = false;
}
}
}
To use it, replace the BusyIndicator tags in your xaml with the new EnhancedBusyIndicator and add the appropriate namespace.
Add a new property, ControlToFocusOn inside the element, and bind it to an existing element in the view that you want focus to be on after the EnhancedBusyIndicator disappears:
<my:EnhancedBusyIndicator
ControlToFocusOn="{Binding ElementName=NameTextBox}"
...
>
...
</my:EnhancedBusyIndicator>
In this case, I focused to a textbox called NameTextBox.
That's it. This control will get focus every time we navigate to the page. While we are on the page, if the EnhancedBusyIndicator becomes busy and not busy agiain, focus will not go to the control; this only happens on initial load.
If you want to allow the EnhancedBusyIndicator to focus to the ControlToFocusOn another time, add another property, AllowedToFocus:
<my:EnhancedBusyIndicator
ControlToFocusOn="{Binding ElementName=NameTextBox}"
AllowedToFocus="{Binding IsAllowedToFocus}"
...
>
...
</my:EnhancedBusyIndicator>
When AllowedToFocus is set to true, the next time EnhancedBusyIndicator switches from busy to not busy, focus will go to ControlToFocusOn.
AllowedToFocus can also be set to false when loading the view, to prevent focus from going to a control. If you bind AllowedToFocus to a ViewModel property, you may need to change the BindingMode. By default, it is OneTime.
How can I tell my TabControl to set the focus to its first TabItem, something like this:
PSEUDO-CODE:
((TabItem)(MainTabControl.Children[0])).SetFocus();
How about this?
MainTabControl.SelectedIndex = 0;
this.tabControl1.SelectedTab = this.tabControl1.TabPages["tSummary"];
I've found it's usually a best practice to name your tabs and access it via the name so that if/when other people (or you) add to or subtact tabs as part of updating, you don't have to go through your code and find and fix all those "hard coded" indexes. hope this helps.
I realise this was answered a long time ago, however a better solution would be to bind your items to a collection in your model and expose a property that selected item is bound to.
XAML:
<!-- MyTemplateForItem represents your template -->
<TabControl ItemsSource="{Binding MyCollectionOfItems}"
SelectedItem="{Binding SelectedItem}"
ContentTemplate="{StaticResource MyTemplateForItem}">
</TabControl>
Code Behind:
public ObservableCollection<MyItem> MyCollectionOfItems {
get;
private set;
}
private MyItem selectedItem;
public MyItem SelectedItem{
get { return selectedItem; }
set {
if (!Object.Equals(selectedItem, value)) {
selectedItem = value;
// Ensure you implement System.ComponentModel.INotifyPropertyChanged
OnNotifyPropertyChanged("SelectedItem");
}
}
}
Now, all you have to do to set the item is:
MyItem = someItemToSelect;
You can use the same logic with the SelectedIndex property, further, you can use the two at the same time.
This approach allows you to separate your model correctly from the UI, which could allow you to replace the TabControl with something else down the line but not requiring you to change your underlying model.
Look at the properties for the tab control...
Expand the TabPages properties "collection"...
Make note of the names you gave the members.
ie. a tab control called tabMain with 2 tabs called tabHeader and tabDetail
Then to select either tab...You have to set it with the tabname
tabMain.SelectedTab = tabHeader;
tabControl1.SelectedTab = item;
item.Focus();
Basically all of the answers here deal with SELECTION, which does not answer the question.
Maybe that is what OP wanted, but the question very specifically asks for FOCUS.
TabItem item = (TabItem)MainTabControl.Items[0];
// OR
TabItem item = (TabItem)MainTabControl.SelectedItem;
// Then
item.Focus();
tabControl.SelectedItem = tabControl.Items[0];
If you have a Tabcontroller named tabControl you could set the selectedIndex from different methods, i use following methods mostly.
codebehind:
tabControl.SelectedIndex = 0; // Sets the focus to first tabpanel
clientside:
First, put the following javascript in your aspx/ascx file:
<script type="text/javascript">
function SetActiveTab(tabControl, activeTabIndex) {
var activeTab = tabControl.GetTab(activeTabIndex);
if(activeTab != null)
tabControl.SetActiveTab(activeTab);
}</script>
Then add following clientside event to prefered controller:
OnClientClick="function(s, e) { SetActiveTab(tabControl, 0);
it's better to use the following type of code to select the particular
item in the particular tab...
.
private void PutFocusOnControl(Control element)
{
if (element != null)
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input,
(System.Threading.ThreadStart)delegate
{
element.Focus();
});
}
And in calling time... tabcontrol.isselected=true;
PutFocusOnControl(textbox1);
will works fine...
Private Sub TabControl1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles TabControl1.SelectedIndexChanged
'MsgBox(TabControl1.SelectedIndex)
If TabControl1.SelectedIndex = 0 Then
txt_apclntFrstName.Select()
Else
txtApplcnNo.Select()
End If
End Sub
It worked for me to set focus to the last tab just after I open it:
//this is my assignment of the collection to the tab control
DictTabControl.DataContext = appTabs.DictTabs;
//set the selected item to the last in the collection, i.e., the one I just added to the end.
DictTabControl.SelectedItem = DictTabControl.Items[(DictTabControl.Items.Count-1)];