I have a tab control that looks like the following:
<TabControl x:Name="tabControl1"
Margin="6,42,12,6"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TabControl.Style>
<Style TargetType="{x:Type TabControl}">
<Setter Property="SelectedIndex" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Test.IsStopped}" Value="True">
<Setter Property="SelectedIndex" Value="5"/>
<Setter Property="Background" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Style>
<TabItem...>
<TabItem...>
...
</TabControl>
IsStopped is a property on a viewmodel (implements INotifyPropertyChanged, etc.) Now, this all works fine, but there is a catch. I also have some code behind for things like "Next" and "Back" buttons that increment the tab index
private void NextButton_Click(object sender, RoutedEventArgs e)
{
int old = tabControl1.SelectedIndex;
try
{
tabControl1.SelectedIndex++;
}
catch (IndexOutOfRangeException)
{
tabControl1.SelectedIndex = old;
}
}
If this code is called at any point, the DataTrigger no longer updates the SelectedIndex. The background still changes to Red (just there to test the binding). I understand dependency property value precedence, but this is annoying as I don't seem to have control over what is going on. What exactly is going on? As a bonus question, I know some debugging tricks in WPF but how could I ever begin to see why this isn't working?
EDIT 10/8/2013 2:45 EST
Well this is interesting...I have changed the NextButton_Click event to the following in a small attempt to emulate what happens when a tab is clicked:
private void NextButton_Click(object sender, RoutedEventArgs e)
{
int old = tabControl1.SelectedIndex;
try
{
TabItem nextItem = ((TabItem)tabControl1.Items[old + 1]);
nextItem.IsSelected = true;
}
catch (IndexOutOfRangeException)
{
tabControl1.SelectedIndex = old;
}
}
and things work fine. Does anyone know if there a reason why setting the index explicitly shows the aforementioned behavior?
Well for debugging purposes, I'd hook up a handler to the SelectionChanged event and start looking at what could possibly be different from when it is triggered by your trigger versus by your try-catch. Also x:Name your DataTrigger binding, and look at it in the code behind at runtime, and be sure that that binding is the same as you expect it to be at all times (my first suspicion was that something might be changing here).
Related
As I'm new in C# WPF, I tried to give it a go, using the trial-and-error approach.
What I would like to do, is to change the appearance of a row in a DataGrid, based on some user action.
However, from reading information over the internet, I've understood that the "right" way to go is:
You should not write a method or a function: you need to write an event.
Okay, okay, so I started doing this in the XAML:
<DataGrid x:Name="dg_Areas" ... LoadingRow="View_dg_Areas"/>
Apparently, the View_dg_Areas event handler should have following signature:
private void View_dg_Areas(object sender, DataGridRowEventArgs e) { }
But then what?
I started, very naïvely, as follows:
private void View_dg_Areas(object sender, DataGridRowEventArgs e)
{
System.Diagnostics.Debug.WriteLine(e.ToString());
System.Diagnostics.Debug.WriteLine(e.Row.ToString());
System.Diagnostics.Debug.WriteLine(e.Row.Item.ToString());
}
The idea was to learn from this how I can find information of the corresponding row (is there a way to read the value of a certain column?), but I didn't get anywhere.
I can tell you that the DataGrid is linked to a DataTable, as follows:
dg_Areas.ItemsSource = dataSet.Tables["Areas"].DefaultView;
How can I link the event parameter e and the DataTable the DataGrid is representing?
You could use a RowStyle with a DataTrigger that binds to one of your properties or columns:
<DataGrid x:Name="dg_Areas">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding YourColumn}" Value="SomeValue">
<Setter Property="Background" Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
</DataGrid>
The above sample markup changes the background colour of the rows where "YourColumn" equals "SomeValue".
Using XAML defined styles to change the visual appearance is considered a best practice. Handling events is not.
I am struggling to wrap my head around the real benefit of binding in WPF.
I have an application with a large textbox, designed for taking several hundred characters of user input. I have bound this to a "Text" string in my ViewModel. This works OK.
I also have a button with content "Submit". I need to change the content of this button once or twice, so I am doing it in the click event method in the window's code behind. I could, of course, bind the text to the ViewModel, but is it really worth it?
Should everything have a binding? What if I need to display a MessageBox? That will need some logic inside the onclick.
Should click events me as follows:
private void button_Login_Click(object sender, RoutedEventArgs e)
{
viewModel.DoSomething();
}
..where everything gets handed to the ViewModel?
I know this is a general question but I have tried my best to ask direct, answerable questions.
UI concerns are perfectly fine residing in your codebehind.
Business concerns should reside in your ViewModels. The commands and information they expose are what should be bound to elements in your UI.
Since changing the text in a button based on what the button is supposed to do is a UI concern, binding the text of the button to your ViewModel would be pointless.
I wouldn't put any code in codebehind. Create an ICommand property in your ViewModel and bind the buttons Command property to it. I use the ICommand implementation from MVVM Light (RelayCommand) but you can create your own or use one of the many other frameworks.
I'd then have a State property (ProcessStatus here) that I use a DataTrigger with to update the text on my button.
ViewModel
public ICommand LoginCommand
{
get
{
return new RelayCommand(() =>
{
ProcessStatus = Status.AUTHORIZING;
DoSomething();
});
}
}
private Status _processStatus;
public Status ProcessStatus
{
get { return _processStatus; }
set
{
if (value ==_processStatus)
return;
_processStatus= value;
RaisePropertyChanged("ProcessStatus");
}
}
View
<Button Command="{Binding LoginCommand}">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Content"
Value="Submit" />
<Setter Property="IsEnabled"
Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding ProcessStatus}"
Value="{x:Static enum:Status.AUTHORIZING}">
<Setter Property="Content"
Value="Authorizing..." />
<Setter Property="IsEnabled"
Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Before I start explaining my issue, please note that my target framework is .NET 3.5.
I have a textbox whose text is bound to a viewmodel property. My requirement is that when user enters something(via keyboard as well as Mouse Paste) into the textbox, any junk characters inside it should be cleaned and the textbox should be updated with the replaced string[In the below example 's' to be replaced with 'h'].
XAMLCode:
<Style x:Key="longTextField" TargetType="{x:Type TextBoxBase}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="AcceptsReturn" Value="True"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBoxBase}">
<Border
Name="Border"
Padding="2"
Background="Transparent"
BorderBrush="LightGray"
BorderThickness="1">
<ScrollViewer Margin="0" x:Name="PART_ContentHost"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="Black"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}" MinLines="3" TextWrapping="Wrap"
SpellCheck.IsEnabled="True" Style="{StaticResource longTextField}"></TextBox>
ViewModel property:
private string _value;
public string Value
{
get
{
return _value;
}
set
{
if (_value == value)
return;
_value = value;
//replaces 's' with 'h' and should update the textbox.
_value = _value.Replace('s','h');
RaisePropertyChanged(() => Value);
}
}
The above is simply not working for me. The view model property setter is firing...the value is getting replaced..however the textbox is not getting updated. What is confusing is that this works perfectly on .Net4.0.
Do you know why this wont work and what is a potential solution to this problem, of course other than upgrading to .NET 4.0?
My requirement:
User can type as well as paste anything into a multilined textbox.
The text can contain junk which should be changed before it comes on to the textbox.
Thanks in advance,
-Mike
I encountered a very similar problem where I wanted the two way binding and I was modifying the value in the ViewModel and expecting to see the update in the TextBox. I was able to get it resolved. Although I was using .NET 4.0, I basically had the same issue, so this may be worth trying out for your situation with 3.5 as well.
Short Answer:
What I encountered was a bug where the TextBox's displayed text was getting out of sync with the value of that TextBox's own Text property. Meleak's answer to a similar question clued me in to this and I was able to verify this with the debugger in Visual Studio 2010, as well as by employing Meleak's TextBlock technique.
I was able to resolve it by using explicit binding. This required handling the UpdateSource() and UpdateTarget() issues myself in code behind (or in a custom control code as I eventually did to make it easier to reuse).
Further Explanation:
Here's how I handled the explicit binding tasks. First, I had an event handler for the TextChanged event which updated the source of the binding:
// Push the text in the textbox to the bound property in the ViewModel
textBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();
Second, in I had an event handler for the TextBox's Loaded event. In that handler, I registered a handler for the PropertyChanged event of my ViewModel (the ViewModel was the "DataContext" here):
private void ExplicitBindingTextBox_Loaded(object sender, RoutedEventArgs e)
{
TextBox textBox = sender as TextBox;
if (textBox.DataContext as INotifyPropertyChanged == null)
throw new InvalidOperationException("...");
(textBox.DataContext as INotifyPropertyChanged).PropertyChanged +=
new PropertyChangedEventHandler(ViewModel_PropertyChanged);
}
Finally, in the PropertyChanged handler, I cause the TextBox to grab the value from the ViewModel (by initiating the UpdateTarget()). This makes the TextBox get the modified string from the ViewModel (in your case the one with replaced characters). In my case I also had to handle restoring the user's caret position after refreshing the text (from the UpdateTarget()). That part may or may not apply to your situation though.
/// <summary>
/// Update the textbox text with the value that is in the VM.
/// </summary>
void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
// This textbox only cares about the property it is bound to
if (e.PropertyName != MyViewModel.ValueStrPropertyName)
return;
// "this" here refers to the actual textbox since I'm in a custom control
// that derives from TextBox
BindingExpression bindingExp = this.GetBindingExpression(TextBox.TextProperty);
// the version that the ViewModel has (a potentially modified version of the user's string)
String viewModelValueStr;
viewModelValueStr = (bindingExp.DataItem as MyViewModel).ValueStr;
if (viewModelValueStr != this.Text)
{
// Store the user's old caret position (relative to the end of the str) so we can restore it
// after updating the text from the ViewModel's corresponding property.
int oldCaretFromEnd = this.Text.Length - this.CaretIndex;
// Make the TextBox's Text get the updated value from the ViewModel
this.GetBindingExpression(TextBox.TextProperty).UpdateTarget();
// Restore the user's caret index (relative to the end of the str)
this.CaretIndex = this.Text.Length - oldCaretFromEnd;
}
}
How to set the focus on an TextBox element in WPF
I have this code:
txtCompanyID.Focusable = true;
txtCompanyID.Focus();
...but it is not working.
Any idea?
In XAML:
<StackPanel FocusManager.FocusedElement="{Binding ElementName=Box}">
<TextBox Name="Box" />
</StackPanel>
Nobody explained so far why the code in the question doesn't work. My guess is that the code was placed in the constructor of the Window. But at this time it's too early to set the focus. It has to be done once the Window is ready for interaction. The best place for the code is the Loaded event:
public KonsoleWindow() {
public TestWindow() {
InitializeComponent();
Loaded += TestWindow_Loaded;
}
private void TestWindow_Loaded(object sender, RoutedEventArgs e) {
txtCompanyID.Focus();
}
}
try FocusManager.SetFocusedElement
FocusManager.SetFocusedElement(parentElement, txtCompanyID)
txtCompanyID.Focusable = true;
Keyboard.Focus(txtCompanyID);
msdn:
There can be only one element on the
whole desktop that has keyboard focus.
In WPF, the element that has keyboard
focus will have IsKeyboardFocused set
to true.
You could break after the setting line and check the value of IsKeyboardFocused property. Also check if you really reach that line or maybe you set some other element to get focus after that.
Try this : MyTextBox.Focus ( );
None of this worked for me as I was using a grid rather than a StackPanel.
I finally found this example:
http://spin.atomicobject.com/2013/03/06/xaml-wpf-textbox-focus/
and modified it to this:
In the 'Resources' section:
<Style x:Key="FocusTextBox" TargetType="Grid">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=textBoxName, Path=IsVisible}" Value="True">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=textBoxName}"/>
</DataTrigger>
</Style.Triggers>
</Style>
In my grid definition:
<Grid Style="{StaticResource FocusTextBox}" />
In case you haven't found the solution on the other answers, that's how I solved the issue.
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
TEXTBOX_OBJECT.Focus();
}), System.Windows.Threading.DispatcherPriority.Render);
From what I understand the other solutions may not work because the call to Focus() is invoked before the application has rendered the other components.
In Code behind you can achieve it only by doing this.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
txtIndex.Focusable = true;
txtIndex.Focus();
}
Note: It wont work before window is loaded
Another possible solution is to use FocusBehavior provided by free DevExpress MVVM Framework:
<TextBox Text="This control is focused on startup">
<dxmvvm:Interaction.Behaviors>
<dxmvvm:FocusBehavior/>
</dxmvvm:Interaction.Behaviors>
</TextBox>
It allows you to focus a control when it's loaded, when a certain event is raised or a property is changed.
I have a tooltip for a Label and I want it to stay open until the user
moves the mouse to a different control.
I have tried the following properties on the tooltip:
StaysOpen="True"
and
ToolTipService.ShowDuration = "60000"
But in both cases the tooltip is only displayed for exactly 5 seconds.
Why are these values being ignored?
If you want to set this for just one tooltip, set the duration on the object having the Tooltip, like this:
<Label ToolTipService.ShowDuration="12000" Name="lblShowTooltip" Content="Shows tooltip">
<Label.ToolTip>
<ToolTip>
<TextBlock>Hello world!</TextBlock>
</ToolTip>
</Label.ToolTip>
</Label>
I'd say that this design was chosen because it allows same tooltip with different timeouts on different controls.
If you want this globally for your whole app, see the accepted answer.
Just put this code in initialization section.
ToolTipService.ShowDurationProperty.OverrideMetadata(
typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));
This was also driving me crazy tonight. I created a ToolTip subclass to handle the issue. For me, on .NET 4.0, the ToolTip.StaysOpen property is not "really" stays open.
In the class below, use the new property ToolTipEx.IsReallyOpen, instead of property ToolTip.IsOpen. You will get the control you want. Via the Debug.Print() call, you can watch in the debugger Output window just how many times this.IsOpen = false is called! So much for StaysOpen, or should I say "StaysOpen"? Enjoy.
public class ToolTipEx : ToolTip
{
static ToolTipEx()
{
IsReallyOpenProperty =
DependencyProperty.Register(
"IsReallyOpen",
typeof(bool),
typeof(ToolTipEx),
new FrameworkPropertyMetadata(
defaultValue: false,
flags: FrameworkPropertyMetadataOptions.None,
propertyChangedCallback: StaticOnIsReallyOpenedChanged));
}
public static readonly DependencyProperty IsReallyOpenProperty;
protected static void StaticOnIsReallyOpenedChanged(
DependencyObject o, DependencyPropertyChangedEventArgs e)
{
ToolTipEx self = (ToolTipEx)o;
self.OnIsReallyOpenedChanged((bool)e.OldValue, (bool)e.NewValue);
}
protected void OnIsReallyOpenedChanged(bool oldValue, bool newValue)
{
this.IsOpen = newValue;
}
public bool IsReallyOpen
{
get
{
bool b = (bool)this.GetValue(IsReallyOpenProperty);
return b;
}
set { this.SetValue(IsReallyOpenProperty, value); }
}
protected override void OnClosed(RoutedEventArgs e)
{
System.Diagnostics.Debug.Print(String.Format(
"OnClosed: IsReallyOpen: {0}, StaysOpen: {1}", this.IsReallyOpen, this.StaysOpen));
if (this.IsReallyOpen && this.StaysOpen)
{
e.Handled = true;
// We cannot set this.IsOpen directly here. Instead, send an event asynchronously.
// DispatcherPriority.Send is the highest priority possible.
Dispatcher.CurrentDispatcher.BeginInvoke(
(Action)(() => this.IsOpen = true),
DispatcherPriority.Send);
}
else
{
base.OnClosed(e);
}
}
}
Small rant: Why didn't Microsoft make DependencyProperty properties (getters/setters) virtual so we can accept/reject/adjust changes in subclasses? Or make a virtual OnXYZPropertyChanged for each and every DependencyProperty? Ugh.
---Edit---
My solution above looks weird in the XAML editor -- the tooltip is always showing, blocking some text in Visual Studio!
Here is a better way to solve this problem:
Some XAML:
<!-- Need to add this at top of your XAML file:
xmlns:System="clr-namespace:System;assembly=mscorlib"
-->
<ToolTip StaysOpen="True" Placement="Bottom" HorizontalOffset="10"
ToolTipService.InitialShowDelay="0" ToolTipService.BetweenShowDelay="0"
ToolTipService.ShowDuration="{x:Static Member=System:Int32.MaxValue}"
>This is my tooltip text.</ToolTip>
Some code:
// Alternatively, you can attach an event listener to FrameworkElement.Loaded
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Be gentle here: If someone creates a (future) subclass or changes your control template,
// you might not have tooltip anymore.
ToolTip toolTip = this.ToolTip as ToolTip;
if (null != toolTip)
{
// If I don't set this explicitly, placement is strange.
toolTip.PlacementTarget = this;
toolTip.Closed += new RoutedEventHandler(OnToolTipClosed);
}
}
protected void OnToolTipClosed(object sender, RoutedEventArgs e)
{
// You may want to add additional focus-related tests here.
if (this.IsKeyboardFocusWithin)
{
// We cannot set this.IsOpen directly here. Instead, send an event asynchronously.
// DispatcherPriority.Send is the highest priority possible.
Dispatcher.CurrentDispatcher.BeginInvoke(
(Action)delegate
{
// Again: Be gentle when using this.ToolTip.
ToolTip toolTip = this.ToolTip as ToolTip;
if (null != toolTip)
{
toolTip.IsOpen = true;
}
},
DispatcherPriority.Send);
}
}
Conclusion: Something is different about classes ToolTip and ContextMenu. Both have "service" classes, like ToolTipService and ContextMenuService, that manage certain properties, and both use Popup as a "secret" parent control during display. Finally, I noticed ALL the XAML ToolTip examples on the Web do not use class ToolTip directly. Instead, they embed a StackPanel with TextBlocks. Things that make you say: "hmmm..."
You probably want to use Popup instead of Tooltip, since Tooltip assumes that you're using it in the pre-defined UI-standards way.
I'm not sure why StaysOpen doesn't work, but ShowDuration works as documented in MSDN -- it's the amount of time the Tooltip is displayed WHEN it's displayed. Set it to a small amount (e.g. 500 msec) to see the difference.
The trick in your case is maintaining the "last hovered control" state, but once you have that it should be fairly trivial to change the placement target and the content dynamically (either manually, or via binding) if you're using one Popup, or hiding the last visible Popup if you're using multiple.
There are some gotchas with Popups as far as Window resizing and moving (Popups don't move w/the containers), so you may want to also have that in mind while you're tweaking the behavior. See this link for more details.
HTH.
If you want to specify that only certain elements in your Window have
effectively indefinite ToolTip duration you can define a Style in your Window.Resources for those elements. Here is a Style for Button that has such a ToolTip :
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
...>
...
<Window.Resources>
<Style x:Key="ButtonToolTipIndefinate" TargetType="{x:Type Button}">
<Setter Property="ToolTipService.ShowDuration"
Value="{x:Static Member=sys:Int32.MaxValue}"/>
</Style>
...
</Window.Resources>
...
<Button Style="{DynamicResource ButtonToolTipIndefinate}"
ToolTip="This should stay open"/>
<Button ToolTip="This Should disappear after the default time.">
...
One can also add Style.Resources to the Style to change the appearance of the ToolTip it shows, for example:
<Style x:Key="ButtonToolTipTransparentIndefinate" TargetType="{x:Type Button}">
<Style.Resources>
<Style x:Key="{x:Type ToolTip}" TargetType="{x:Type ToolTip}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="HasDropShadow" Value="False"/>
</Style>
</Style.Resources>
<Setter Property="ToolTipService.ShowDuration"
Value="{x:Static Member=sys:Int32.MaxValue}"/>
</Style>
Note: When I did this I also used BasedOn in the Style so everything else defined for the version of my custom control with a normal ToolTip would be applied.
I was wrestling with the WPF Tooltip only the other day. It doesn't seem to be possible to stop it from appearing and disappearing by itself, so in the end I resorted to handling the Opened event. For example, I wanted to stop it from opening unless it had some content, so I handled the Opened event and then did this:
tooltip.IsOpen = (tooltip.Content != null);
It's a hack, but it worked.
Presumably you could similarly handle the Closed event and tell it to open again, thus keeping it visible.
Just for the sake of completeness:
In code it looks like this:
ToolTipService.SetShowDuration(element, 60000);
Also if you ever want to put any other control in your ToolTip, it won't be focusable since a ToolTip itself can get focus. So Like micahtan said, your best shot is a Popup.
Got my issue fixed with the same code.
ToolTipService.ShowDurationProperty.OverrideMetadata(
typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));
(almost) Simple XAML version for ALL tooltips:
Put this style in your window resources (and add the missing types you commonly use)
<Style x:Key="LongToolTipStyle" TargetType="FrameworkElement">
<Setter Property="ToolTipService.ShowDuration" Value="20000"/>
</Style>
<Style TargetType="TextBlock" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="Button" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="TextBox" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="RadioButton" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="CheckBox" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="ComboBox" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="Grid" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="StackPanel" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="DockPanel" BasedOn="{StaticResource LongToolTipStyle}"/>
<Style TargetType="Image" BasedOn="{StaticResource LongToolTipStyle}"/>
Might be a bit boring, but you can keep it in a resource dictionary.
It's sad that we can't apply these styles to subclasses.
Downside
Whenever you create another style, you must remember to make that style BasedOn="{StaticResource LongToolTipStyle}"
ToolTipService.ShowDurationProperty.OverrideMetadata(
typeof(DependencyObject), new FrameworkPropertyMetadata(Int32.MaxValue));
It is working for me. Copy this line into your class constructor.