I am experiencing a very weird problem: In WPF I have a tabControl which contains 3 tabItems. I have made a binding from the tabControl's SelectedIndex to a property inside my viewModel class in "OneWayToSource" mode.
Here's the XAML code:
<TabControl Name="mainTabControl" SelectedIndex="{Binding SelectedTabIndex, Mode=OneWayToSource}" >
<TabItem Header="Tab 01" Name="tab01"> ... </TabItem>
<TabItem Header="Tab 02" Name="tab02"> ... </TabItem>
<TabItem Header="Tab 03" Name="tab03"> ... </TabItem>
</TabControl>
And in my viewModel:
private int m_selectedTabIndex;
public int SelectedTabIndex
{
get
{ return m_selectedTabIndex; }
set
{
SetAndNotify(ref m_selectedTabIndex, value, () => SelectedTabIndex);
SelectedTabChanged();
}
}
private void SelectedTabChanged()
{
// Some code
}
As you can see, everytime my viewModel's SelectedTabIndex property changes, the SelectedTabChanged() method is executed, this works perfectly.
My weird problem is that: When I show a message using for example System.Windows.MessageBox.Show("Some Text") inside my SelectedTabChanged() method, I select another tabItem and the previous selected tab gets blocked, it looks like selected, but it remains selected permanently, I cannot see its content anymore.
Just to clarify: As I stated before, this weird issue only happens when a modal window is showed
Why is happening? How can I solve this issue?
I hope I explained myself clearly.
Thank you in advance.
I have solved my problem. Since I am new in WPF I really dont understand why a modal window make the tabs get blocked. But I was searching and found that the Dispatcher class allows one to execute asynchronously a method which prevent any control from get blocked.
I changed my viewModel code as below:
public int SelectedTabIndex
{
get
{ return m_selectedTabIndex; }
set
{
SetAndNotify(ref m_selectedTabIndex, value, () => SelectedTabIndex);
Dispatcher.CurrentDispatcher.BeginInvoke(new Action(SelectedTabChanged), null);
}
}
The line that really helped me was the following:
Dispatcher.CurrentDispatcher.BeginInvoke(new Action(SelectedTabChanged), null);
Hope this help someone else can be experiencing some similar problem.
Im not 100% sure I understand your problem, but a MessageBox.Show will spawn a nested message pump and block the main Dispatcher, thus 'freezing' the main window (which is why it is modal).
If you need to show a MessageBox style alert without it being modal, then my advice is to create a panel with your message in it that exists in the TabItem with a hidden visibility, then make it visible when required to show you message. Your Tab selections should still work in that scenario
Related
My app is blocking when i want to navigate to certain view, so i want to show a load icon. The problem is the icon never shows when expected. The view changes and i never see the icon, but if i go back the icon is there.
I tried using an async task to do the navigation, but the navigation doesn't work in a task, i guess.
Any suggestions or ideas?
XAML Code:
<UserControl xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:fa="http://schemas.fontawesome.io/icons/">
<Grid>
<Listbox ItemsSource={Binding ItemsList}>
</Listbox>
<Canvas Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" Margin="-550, 180, 0, 0">
<fa:ImageAwesome Visibility="{Binding LoadingIcon, UpdateSourceTrigger=PropertyChanged}"
Icon="Spinner"
Spin="True"
Canvas.Left="56"
Canvas.Top="-17" />
</Canvas>
</Grid>
<UserControl.InputBindings>
<KeyBinding Key="Enter" Command="{Binding NavigateToMainMenuCommand}"/>
</UserControl.InputBindings>
</UserControl>
ViewModel:
public class LoginViewModel: ViewModelBase, INotifyPropertyChanged, INavigationAware
{
public InicioContableViewModel(IRegionManager regionManager,
IEventAggregator eventAggregator)
{
_regionManager = regionManager;
_eventAggregator = eventAggregator;
NavigateToMainMenuCommand = new DelegateCommand(NavigateToMainMenu);
LoadingIcon = Visibility.Hidden;
}
public DelegateCommand NavigateToMainMenuCommand { get; private set; }
private Visibility loadingIcon;
public Visibility LoadingIcon
{
get
{
return loadingIcon;
}
set
{
SetProperty(ref loadingIcon, value, nameof(LoadingIcon));
NotifyPropertyChanged(nameof(LoadingIcon));
}
}
private void NavigateToMainMenu()
{
LoadingIcon = Visibility.Visible;
string mainUri = nameof(SomeRoute.MainMenu);
_regionManager.RequestNavigate(Regions.MainRegion, mainUri);
}
}
In your Icon's properties, try to set Build Action to Resource.
WPF uses the UI thread to do things like handle mouse movement, change icons when you tell it to and to switch out one view for another. All one thread.
When that thread is busy doing something then nothing is likely to change in your UI until it's finished.
Hence, if you tell it to make something visible AND navigate, you can well find all that happens visually is the navigation. Because it has no time to show your icon.
Try making your method async and giving it a little time:
private async void NavigateToMainMenu()
{
LoadingIcon = Visibility.Visible;
await Task.Delay(60);
string mainUri = nameof(SomeRoute.MainMenu);
_regionManager.RequestNavigate(Regions.MainRegion, mainUri);
}
That task.delay should give it enough time to redraw a bit of ui.
If you need a loading icon and the thing you are navigating to is blocking then I suspect you have other problems. It's likely that whatever this new view is doing to initialise should be asynchronous. Maybe with data access etc on background threads.
You may most likely make use of the functions:- InvalidateVisual() or UpdateLayout(). Those will force to redraw and may resolve your icon visibility issues.
My app is blocking when i want to navigate to certain view, so i want to show a load icon.
If the app is blocking, you cannot show the icon, because, you know, the app is blocked. You have to remove the blocking, that is, make navigation itself quick, and do whatever initialization asynchronously. The view you're navigating to may need stuff from a database or connection to a usb device, but it won't need it immediately. You can still query the database or whatever in the background after the view is shown.
show wait indicator-> navigate -> creates view model -> starts initializion task -> initialization completes -> hide wait indicator
In my WPF app, I've to show different controls in the window for different user actions. Like, when user selects an image, I show a Image control and when the user selects a text file, I show a TextBox control. Similarly there are many controls for different user selections.
To do this I'm first doing Visibility = Visibility.Collapsed for all controls using a foreach loop, then doing Visibility = Visibility.Visible for the controls that I've to show.
Is there a more efficient way of doing this? The window flickers and is not really snappy when changing controls as there are many controls.
I always use a ContentControl for this. It produces very clean xaml code and is superfast, I've never seen any flicker even in rather loaded views. It does require any extra frameworks beyond WPF.
<ContentControl Content="{Binding Selected}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type viewModels:FirstOne}">
<!-- View code for first view goes here -->
<TextBlock>Hi</TextBlock>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:SecondOne}">
<!-- View code for second view goes here -->
<Image Source="{Binding Image}" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
And then I have a MainViewModel handing the navigation with a property
class MainViewModel: ViewModel, INavigation
{
public ViewModel Selected
{
get { return _selected; }
private set
{
_selected = value;
RaisePropertyChanged();
}
public void Show(ViewModel viewModel) { Selected = viewModel; }
}
}
The INavigation interface is just something like so
interface INavigation { void Show(ViewModel viewModel); }
An example view model
class FirstOne: ViewModel
{
private readonly INavigation _navigation;
public FirstOne(INavigation navigation) { _navigation = navigation; }
public void ButtonClicked()
{
_navigation.Show(new SecondOne());
}
}
First, you may want to avoid doing Visibility=Visibility.Collapsed for controls that you are about to make visible. That could reduce some unnecessary screen activity.
Second, rather than having the user experience all the individual visibility changes in a serial fashion, you might want to keep your controls in a parent control that you can hide before the visibility changes take place and then unhide once the changes are complete. You could even use an opacity animation to fade it out and then back in. Of course, without seeing or understanding more about your app, I can't tell what's really appropriate.
Basis: I am using the MVVM pattern or a subset of it.
In my main window I have a button that opens up a usercontrol with a new DataContext the function looks kinda like this:
public void SetUserControl()
{
UCDatacontext = new UCViewModel(this);
base.OnPropertyChanged("UCDatacontext");
UCViewVisibilty = Visibility.Visible;
UCDatacontext.IniFocus();
}
And then when I am done I close the usercontrol and Dispose of the DataContext. Now the problem I am having is that I can't seem to get the focus setting to work properly, I have a Textbox in the usercontrol that I want to set focus to when the view becomes Visible. However on the first time that I attempt to set focus it only fills the text box with an unblinking caret, which after investigation leads me to believe that it is because the TB isn't getting the Keyboard focus (only logical focus), however even after explicity setting the keyboard focus I still get the unblinking caret, and it is only after clicking in the TB that it is getting focus. The method I am using to set focus is similar to method described here.
If in the view I do some writeline debugging by printing out in the FocusSet Event for the textbox it does get set, however only on the first time I call SetUserControl(). If I call SetUserControl() again it does nothing, except making the View Visible but doesn't trigger the Focus Set Event.
Below is the lines of code from the MainWindow:
<Grid Grid.ColumnSpan="5" Grid.RowSpan="5" Visibility="{Binding Path=UCViewVisibilty }" x:Name="UCGrid" >
<Grid.Effect>
<DropShadowEffect />
</Grid.Effect>
<View:UCView DataContext="{Binding Path=UCDatacontext}" />
</Grid>
And UserControl Grid:
<Grid >
<TextBox Uid="UCTB" localExtensions:FocusExtension.IsFocused="{Binding Path=UCTBFocus}" Height="23" HorizontalAlignment="Left" Margin="113,56,0,0" Name="UCTB" VerticalAlignment="Top" Width="165" Text="{Binding Path=UCTBContent, UpdateSourceTrigger=PropertyChanged}" GotFocus="UCTB_GotFocus" />
</Grid >
The Focus is set in the UserControlViewModel, and is set after the Usercontrol is rendered.
it like this Set focus one by one from top to bottom.
InitializeComponent();
FocusManager.SetFocusedElement(this, TabItem); //this is Window , TabItem is UserControl in this Window
FocusManager.SetFocusedElement(TabItem, TextBox); // TabItem is UserControl and TextBox is Control in TabItem UC
I hope this will help.
As it turns out, after fiddling around with the code, the reason why the focus wasn't being set properly in the View was because the binding in the View Model was this:
bool _tBfocus;
public bool UCTBFocus
{
get { return _tBfocus; }
set
{
_tBfocus= value;
base.OnPropertyChanged("UCTBFocus");
}
instead of:
bool _tBfocus;
public bool UCTBFocus
{
get { return _tBfocus; }
set
{
if (_tBfocus == value)
return;
_tBfocus= value;
base.OnPropertyChanged("UCTBFocus");
}
}
After changing it everything worked fine :/ but if someone could explain to me why this annoyance I was having was caused by that I would be truly grateful :)
I'm working on trying to implement validation in WPF, and running into an issue where I can't click on or change the value of a textbox after validation fails.
I have a User class (which implements IDataErrorInfo), which contains the following relevant code:
public virtual string Error
{
get
{
return null;
}
}
public virtual string this[string name]
{
get
{
string result = null;
if (name == "uFirstName")
{
if (String.IsNullOrEmpty(this.uFirstName))
{
return "Must enter a first name!";
}
}
return result;
}
}
Then, over in my MainWindow code-behind, I have this code to hookup my combobox:
comboBox1.ItemsSource = Users; //Users is a collection of Users
Finally, in my MainWindow xaml, I have this:
<ComboBox Name="comboBox1" ItemTemplate="{StaticResource userTemplate}" />
<TextBox Name="textBox1" DataContext="{Binding ElementName=comboBox1,
Path=SelectedItem}" Text="{Binding Path=uFirstName, ValidatesOnDataErrors=True,
NotifyOnValidationError=True}"/>
And indeed, the validation does fire when I delete the text and the textbox gets a nice red border. However, the changes are still sent back to Users (uFirstName gets set to nothing!). Even worse though, I now cannot edit the value in that textbox unless I tab back into it.
What is needed to make sure the value isn't sent back if it isn't valid, and allow the textbox to be edited if is invalid?
The IDataErrorInfo implementation is only there to report errors. You should implement your verification in the uFirstName property. If you don't think a certain verification on a property is true for all instances, you should start looking into ValidationRule. This is a better way of implementing Validation in WPF. ValidationRule will not set value to the source, if the validation fails.
The textbox might be acting strange because of a system level style? Not exactly sure on why you are having trouble there.
I have a kinda awful problem with my WPF application right now...
I have a custom UserControl used to edit details of a component. It should start by being not enabled, and become enabled as soon as the user chose a component to edit.
The problem is: the IsEnabled property does not even change.
Here is my code:
<my:UcComponentEditor Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
IsEnabled="{Binding EditorEnabled}"
DataContext="{Binding VmComponent}" />
EditorEnabled is a property in my ViewModel (VmComponent), and is by default false, becomes true when the user chose a component or created one
Just for the record, in my ViewModel:
private Boolean _editorEnabled = false;
public Boolean EditorEnabled
{
get { return _editorEnabled; }
set
{
_editorEnabled = value;
OnPropertyChanged("EditorEnabled");
}
}
When I try to launch my app, the UserControl is starting... enabled.
I added breakpoints everywhere, the EditorEnabled is false from the beginning.
I also did a horribly stupid thing to try to figure out what's happening: I created a converter (so useful -- converting a boolean to boolean -- eh), put a breakpoint on it, and... The code is never reached.
<my:UcComponentEditor Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
IsEnabled="{Binding EditorEnabled, Converter={StaticResource BoolConverter}}"
DataContext="{Binding VmComponent}" />
That probably means that the property isEnabled is never set, since the converter is never reached.
Do you see any kind of problem there? I started working in WPF about one week ago and therefore I may have missed something essential...
Thank you very much for your time :-)
You should add a DependencyProperty for the binding to work properly. See here for more information.
Code-behind:
public static readonly DependencyProperty EditorEnabledDependencyProperty = DependencyProperty.Register("EditorEnabled", typeof(bool), typeof(UcComponentEditor), new PropertyMetadata(false));
public bool EditorEnabled
{
get { return (bool)base.GetValue(UcComponentEditor.EditorEnabledDependencyProperty); }
set { base.SetValue(UcComponentEditor.EditorEnabledDependencyProperty, value); }
}
The issue I think is that there is a binding on the DataContext property of the user control. Which means the EditorEnabled property should be a property in the VmComponent object. At least that's what my problem was.
To get around it, I specified a proper source to the binding of IsEnabled. Once I did that the control started working as expected.
Hope that helps.
Encapsulating your control in a DockPanel (for example) will remove the need for a DependencyProperty.
You can then simply do your binding with the dockpanel instead of the custom control. Setting the variable bound to IsEnabled on the Dockpanel will automatically enable or disable the items contained in the Dockpanel.