I'm trying to bind a TextBox to the selected date on a Calendar control, and when it initializes, there is no issue. The problem is that after, when I change the selected date, the TextBox remains at its initial value (today). I have tried 3 methods, including simply returning to TextBox.Text = Calendar.DisplayDate.ToString(), but the problem persists.
Does anybody know either what causes this, or a way around it?
Note that PropertyChanged is not null in Method 2.
My code is as follows, with the other two methods implemented:
XAML:
<Calendar Grid.Column="1" Height="170" HorizontalAlignment="Left" Name="calStart" VerticalAlignment="Top" Width="180" IsTodayHighlighted="False" SelectedDatesChanged="CalStartSelectedDatesChanged">
<Calendar.CalendarDayButtonStyle>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource conv}}" Value="1">
<Setter Property="Button.Background" Value="LightGreen" />
</DataTrigger>
</Style.Triggers>
</Style>
</Calendar.CalendarDayButtonStyle>
</Calendar>
<TextBox Height="23" HorizontalAlignment="Left" Margin="34,33,0,0" Text="{Binding StartBindProp, Mode=OneWay}" Name="txtStartDate" VerticalAlignment="Top" Width="120" Grid.Column="1" Grid.Row="1" />
C#
Method 1:
private void CalStartSelectedDatesChanged(object sender, SelectionChangedEventArgs e)
{
StartBindProp = calStart.DisplayDate.ToString();
}
public string StartBindProp
{
get { return (string)GetValue(StartBindPropProperty); }
set { SetValue(StartBindPropProperty, value); }
}
// Using a DependencyProperty as the backing store for StartBindProp. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StartBindPropProperty =
DependencyProperty.Register("StartBindProp", typeof(string), typeof(MainControl), new UIPropertyMetadata(""));
Method 2:
private void CalEndSelectedDatesChanged(object sender, SelectionChangedEventArgs e)
{
EndBind = calEnd.DisplayDate.ToString();
}
private string m_EndBind = "endtest";
public string EndBind
{
get { return m_EndBind; }
set
{
m_EndBind = value;
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs("EndBind"));
}
}
}
Thanks for the help!
EDIT:
The following xaml has the same issue (and apparently renders the calendar read-only):
<TextBox Text="{Binding ElementName=calStart, Path=DisplayDate, Mode=OneWay}" />
Use Calendar.SelectedDate (or SelectedDates if multiple) instead of DisplayDate
I believe the DisplayDate is used to determine which date has the "selected" outline around it in the calendar (since multiple dates can be selected), while SelectedDate is the actual value of the control.
You can find the MSDN docs on the Calendar control here
Related
I have a datagridview populated with items and I am using a SelectionChanged event to populate textboxes from that data when selected.
If I make a selection, everything works. If I click elsewhere in the App and then come back to click the SelectionChanged event again on the same item - it doesn't work.
According to MSDN:
"This event occurs whenever there is a change to a selection."
MSDN SelectionChangedEvent
So it appears that despite clicking elsewhere, resetting the Textboxes - the selected item is not changing as the SelectionChanged event no longer triggers - click on another item and it works, click back again and it works - but click on it, reset textboxes, click it again - nothing happens, this includes clicking in the datagridview itself in a blank area.
XAML:
<DataGrid x:Name="TimeView" Grid.Row="1" Grid.Column="3"
Grid.ColumnSpan="3" Grid.RowSpan="4" Margin="10 50 10 10"
CanUserAddRows="False" Visibility="{Binding StartTiming}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cal:ActionMessage MethodName="SelectedTimeChangeEvent">
<cal:Parameter Value="$eventArgs" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
ViewModel
public void SelectedTimeChangeEvent(SelectionChangedEventArgs e)
{
foreach (TimeData addedRow in e.AddedItems)
{
TbID = addedRow.ID;
TbDate = addedRow.Date;
TbStartTime = addedRow.StartTime;
TbDescription = addedRow.Description;
}
}
Since I am using MVVM and Caliburn, TimeView is connected to an ICollection, which is in turn connected to an ObservableCollection:
private ObservableCollection<TimeData>? _timeCollection;
public ObservableCollection<TimeData>? TimeCollection
{
get { return _timeCollection; }
set
{
_timeCollection = value;
NotifyOfPropertyChange(() => TimeCollection);
}
}
private ICollectionView? _timeView;
public ICollectionView? TimeView
{
get { return _timeView; }
set
{
_timeView = value;
NotifyOfPropertyChange(() => TimeView);
}
}
There is a work around, which is the following after populating the Textboxes:
TimeView = null;
TimeView = CollectionViewSource.GetDefaultView(TimeCollection);
This works, but I thought that there might be a "deselect" option that would be better than repopulating every time a selection is made, one of my Datagrids contains 15,000 items, and it is still instant, but seems overkill to populate it every time a selection is made.
i would recommend bindings, they automaticly reset when nothing is selected
<DockPanel>
<StackPanel DataContext="{Binding SelectedTime}" DockPanel.Dock="Left">
<TextBlock Text="{Binding ID}"/>
<TextBlock Text="{Binding Date}"/>
<TextBlock Text="{Binding StartTime}"/>
<TextBlock Text="{Binding Description}"/>
</StackPanel>
<DataGrid ItemsSource="{Binding TimeView}" SelectedItem="{Binding SelectedTime}">
...
</DataGrid>
</DockPanel>
public TimeData SelectedTime
{
get { return _selectedTime; }
set
{
_selectedTime = value;
NotifyOfPropertyChange(() => SelectedTime);
}
}
also there is this neet feature
protected virtual void SetValue<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
field = value;
OnPropertyChanged(propertyName);
}
so you can write
set { SetValue(ref _selectedTime, value) }
I have created a custom control, which inherits TextBox. It basically has an extra property 'Seconds' and set a binding on 'Text', to shown the 'Seconds' formatted, as eg. 2m 5s, using a converter.
I now want to default right-align the text.
From other custom controls I know we sometimes will want to set/override values using styles. If I set the value directly in the constructor I will not be able to do this.
I would usually something like this:
TextAlignmentProperty.OverrideMetadata(typeof(DurationTextBox), new FrameworkPropertyMetadata(TextAlignment.Right));
But this does NOT appear to work:
First two have the default alignment, then they are Left, Center and Right aligned directly on the controls. Seconds row has a style setting alignment to Center
I have bound a TextBlock to the TextAlignment of the first DurationTextBox, and this states that the aligment is 'Right', but this is not how it is shown!
Can anyone explain:
A. Why this is not working?
B. How to do this correctly, or something with the same end effect? (default aligned Right, but possible to override from Style)
C# class :
Please note that this is a simplified version. The complete one has Min, Max, option of confirming value changed and option for out of range action, which is the reason for the structure of the class. Please keep focus on the TextAlignment issue!
(The SecondsToDurationStringConverter and DurationStringValidator can be removed to make the example compile with the same effect)
public class DurationTextBox : TextBox
{
#region Dependency properties
/// <summary>
/// Property for <see cref="Seconds"/>
/// </summary>
[NotNull] public static readonly DependencyProperty SecondsProperty = DependencyProperty.Register(nameof(Seconds), typeof(double), typeof(Demo.DurationTextBox), new FrameworkPropertyMetadata(default(double), SecondsChangedCallback) { BindsTwoWayByDefault = true });
/// <summary>
/// Seconds to show as duration string
/// </summary>
public double Seconds
{
// ReSharper disable once PossibleNullReferenceException
get { return (double)GetValue(SecondsProperty); }
set { SetValue(SecondsProperty, value); }
}
/// <summary>
/// Property for <see cref="EditValue"/>
/// </summary>
[NotNull] public static readonly DependencyProperty EditValueProperty = DependencyProperty.Register(nameof(EditValue), typeof(double), typeof(Demo.DurationTextBox), new FrameworkPropertyMetadata(default(double), EditValueChangedCallback) { BindsTwoWayByDefault = true });
/// <summary>
/// Number being edited by the actual text box. Transferred to <see cref="Seconds"/>.
/// <para>Do NOT bind to this property from outside this control</para>
/// </summary>
public double EditValue
{
// ReSharper disable once PossibleNullReferenceException
get { return (double)GetValue(EditValueProperty); }
set { SetValue(EditValueProperty, value); }
}
#endregion Dependency properties
private bool _isLocked;
static DurationTextBox()
{
// TextAlignment
TextAlignmentProperty.OverrideMetadata(typeof(Demo.DurationTextBox), new FrameworkPropertyMetadata(TextAlignment.Right));
}
/// <inheritdoc />
public DurationTextBox()
{
SecondsToDurationStringConverter secondsToDurationStringConverter = new SecondsToDurationStringConverter();
// Text
Binding binding = new Binding(nameof(EditValue)) { Source = this, Converter = secondsToDurationStringConverter, NotifyOnValidationError = true };
binding.ValidationRules.Add(new DurationStringValidation());
SetBinding(TextProperty, binding);
}
private static void SecondsChangedCallback([CanBeNull] DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Demo.DurationTextBox durationTextBox = d as Demo.DurationTextBox;
if (durationTextBox == null) return;
if (!durationTextBox._isLocked)
{
durationTextBox._isLocked = true;
durationTextBox.SetCurrentValue(EditValueProperty, durationTextBox.Seconds);
durationTextBox._isLocked = false;
}
}
private static void EditValueChangedCallback([CanBeNull] DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Demo.DurationTextBox durationTextBox = d as Demo.DurationTextBox;
if (durationTextBox == null) return;
if (!durationTextBox._isLocked)
{
durationTextBox._isLocked = true;
durationTextBox.SetCurrentValue(SecondsProperty, durationTextBox.EditValue);
durationTextBox._isLocked = false;
}
}
}
XAML code:
<Label Content="Demo.DurationTextBox" FontWeight="Bold"/>
<WrapPanel>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" x:Name="DemoDurationTextBox"/>
<TextBlock Text="{Binding ElementName=DemoDurationTextBox, Path=TextAlignment}"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Left"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Center"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Right"/>
</WrapPanel>
<WrapPanel>
<WrapPanel.Resources>
<Style TargetType="demo:DurationTextBox">
<Setter Property="TextAlignment" Value="Center"/>
</Style>
</WrapPanel.Resources>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}"/>
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Left" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Center" />
<demo:DurationTextBox MinWidth="150" Seconds="{Binding ElementName=Duration1, Path=Text}" TextAlignment="Right" />
</WrapPanel>
A: This isn't working because TextBox.TextAlignment is inherited, so by default the active value is determined by the parent control at runtime. It doesn't make sense for a class to override the default value of the static DependencyProperty object itself.
The fact that the debug binding you added displays "Right" is strange, and probably an internal WPF optimisation glitch.
B: The correct solution is to set TextBox.TextAlignment="Right" (note the type name qualifier) on a parent control, e.g. your wrap panel. That value will then be automatically applied to all child text blocks unless they or an intermediate parent override it further, including via a style.
C: I would add that the code you posted seems to be an attempt to re-invent DataGrid. That control supports transactional editing out of the box and might save you a lot of time if you switch to it!
While it is not the solution I would have preferred, I have found a way that allows me to have the default alignment of my control to Right, while being able to overwrite it locally by using a style, or directly on the control, without affecting other TextBoxes
I made a default style:
<Style TargetType="controls:DurationTextBox">
<Setter Property="TextAlignment" Value="Right"/>
</Style>
As I have other resource dictionaries which must be included anyway this will work in my situation.
I would have preferred simply setting the default value of my control, but according to Artfunkel that is sadly not possible.
The issue with my chosen approach of using a default style is if a different default style is set for TextBox in a project, the DurationTextBox will not use/inherit this style, because it has its own default style, thus someone using the library has to also set a similar style/override the style for the DurationTextBox for them not to appear different.
If the DurationTextBox had not needed a different default style, containing the text alignment, it would have been possible to have the default style be the same as a TextBox, but this is not a possibility now.
As we do have a different default style for TextBox I have added a BasedOn to my default style:
<Style TargetType="controls:DurationTextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="TextAlignment" Value="Right"/>
</Style>
I would like to binding as below:
A (in View) <---> B (in View) <----> C (MyData)
Binding A-B : Two way
Binding B-C : One way
Xaml
<Slider x:Name="slider" Minimum="0" Maximum="100" Value="{Binding Path=Text, ElementName=textBox, Mode=TwoWay}"/>
<TextBox x:Name="textBox" Text="{Binding Path=MyValue, Mode=OneWay}"/>
<Button x:Name="button" Content="Button" Click="button_Click"/>
Code Behind:
public int MyValue
{
get { return m_value; }
set
{
m_value = value;
NotifyPropertyChanged("MyValue");
}
}
...
private void button_Click(object sender, RoutedEventArgs e)
{
MyValue = 10;
}
I expect that when I click on button, the value of text box and slider bar will be updated.
However, nothing happen.
Please help me to solve this problem.
Thank in advance.
What you are trying to do is wrong in the architecture, that's why i would suggest you to take a look at some documentation about MVVM
such as:
https://www.codeproject.com/Articles/819294/WPF-MVVM-step-by-step-Basics-to-Advance-Level
or instead of considering just PRISM there are some more that are powerful but more approchable and easier such as MvvmLight:
http://www.dotnetcurry.com/wpf/1037/mvvm-light-wpf-model-view-viewmodel
Hope having been useful
Need to show a hint, which contains data from a text field. Prompt to appear if the textbox has data.
Just use binding to ToolTipService attached properties. XAML:
<UserControl.Resources>
<converters:IsStringNonemptyConverter x:Key="ToolTipVisibilityConveter" />
</UserControl.Resources>
<TextBox Name="textBox" VerticalAlignment="Center" HorizontalAlignment="Center" Width="150"
ToolTipService.ToolTip="{Binding Text, RelativeSource={RelativeSource Self}}"
ToolTipService.IsEnabled="{Binding Text, RelativeSource={RelativeSource Self}, Converter={StaticResource ToolTipVisibilityConveter}}"/>
Converter:
internal sealed class IsStringNonemptyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return !String.IsNullOrEmpty(value as string);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You can disable the tooltip using triggers. Place this style in your window or App resources so that it can be shared across all the textboxes in your window or application depending on your choice -
<Style x:Key="{x:Type TextBox}" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="ToolTip" Value="{x:Static sys:String.Empty}">
<Setter Property="ToolTipService.IsEnabled" Value="False" />
</Trigger>
</Style.Triggers>
Make sure you add the system namespace to your xaml -
xmlns:sys="clr-namespace:System;assembly=mscorlib"
I had this problem myself and figured out a different solution. I know this question has been answered but just like me there will still be people coming across this question, and I would like to share my solution:
XAML
<TextBox Name="textBox1" ToolTip="{Binding Text, RelativeSource={RelativeSource Self}}" ToolTipService.IsEnabled="False"/>
Code behind
private void textBox1_TextChanged(object sender, TextChangedEventArgs e)
{
if (textBox1.Text.Length > 0)
{
ToolTipService.SetIsEnabled(textBox1, true);
}
}
I hope this helps someone.
I tried with Visibility Mode & TextChange event. ToolTip invisible when no text. May be useful for someother.
Xaml:
<TextBox Height="23" Width="100" Name="myTextBox" TextChanged="myTextBox_TextChanged" >
<TextBox.ToolTip>
<ToolTip Visibility="Hidden">
<TextBlock Name="toolTipTextBlock"></TextBlock>
</ToolTip>
</TextBox.ToolTip>
</TextBox>
TextChange event handler:
private void myTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
TextBox tb = sender as TextBox;
if (tb.Text.Trim() == "")
{
((ToolTip)tb.ToolTip).Visibility = Visibility.Hidden;
}
else
{
toolTipTextBlock.Text = tb.Text;
((ToolTip)tb.ToolTip).Visibility = Visibility.Visible;
}
}
I have the following Attached Property:-
public partial class GridViewProperties
{
public static readonly DependencyProperty DoAutoSizeColumnsProperty =
DependencyProperty.RegisterAttached("DoAutoSizeColumns",
typeof(bool),
typeof(GridViewProperties),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault |
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsParentArrange |
FrameworkPropertyMetadataOptions.AffectsParentMeasure |
FrameworkPropertyMetadataOptions.AffectsRender,
DoAutoSizeColumnsChanged));
public static bool GetDoAutoSizeColumns(DependencyObject obj)
{
return (bool)obj.GetValue(DoAutoSizeColumnsProperty);
}
public static void SetDoAutoSizeColumns(DependencyObject obj, bool value)
{
obj.SetValue(DoAutoSizeColumnsProperty, value);
}
private static void DoAutoSizeColumnsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var gv = obj as GridView;
if (gv == null)
return;
if (e.NewValue != null && (bool)e.NewValue)
{
AutoSizeColumns(gv.Columns);
SetDoAutoSizeColumns(gv, false);
gv.InvalidateProperty(GridView.ColumnCollectionProperty);
}
}
private static void AutoSizeColumns(GridViewColumnCollection gvcc)
{
// same code as double clicking column gripper
foreach (var gvc in gvcc)
{
// if already set to auto, toggle it so that we can re-run width="auto"
//if (double.IsNaN(gvc.Width))
gvc.Width = gvc.ActualWidth;
// now do it
gvc.Width = double.NaN;
//gvc.InvalidateProperty(GridViewColumn.WidthProperty);
//gvc.ClearValue(GridViewColumn.WidthProperty);
}
}
}
I use it in XAML in the following fashion:
<Style x:Key="AutoColumnStyle" TargetType="ListView">
<Setter Property="View">
<Setter.Value>
<GridView infra:GridViewProperties.AutoSizeColumns="{Binding Path=DataContext.DoAutoSizeColumns, Source={x:Reference uc}}">
<GridViewColumn Width="auto" Header="Title" CellTemplate="{StaticResource Name}" />
<GridViewColumn Width="auto" Header="First" CellTemplate="{StaticResource First}"/>
<GridViewColumn Width="auto" Header="Last" CellTemplate="{StaticResource Last}"/>
</GridView>
</Setter.Value>
</Setter>
The abaove is in UserControl.Resources.
The rest of the XAML is:
<ListView ItemsSource="{Binding Names}" VerticalAlignment="Top" HorizontalAlignment="Left"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
SelectionMode="Single"
x:Name="ListViewContracts"
KeyboardNavigation.TabNavigation="Continue"
KeyboardNavigation.DirectionalNavigation="Cycle"
Style="{StaticResource AutoColumnStyle}"
>
<ListView.ItemContainerStyle >
<Style BasedOn="{StaticResource ListViewItemContainerStyle}" TargetType="ListViewItem">
<Setter Property="Focusable" Value="False" />
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
(I have tried this with no Width="auto" too).
Whenever I set DoAutoSizeColumns = true in my ViewModel I see everything work as expected in the attached property except what it is desgined for that is the gridview columns are not resized according to the largest item in that column (even though I see gv.Width toggled from and to double.Nan which is how resize is meant to work).
As you can see I have tried a number of variations in the attached property mostly commented out including adding in all the FrameworkPoprertyMetadataOptions and trying various InvalidatePoperty attempts but also UIPropertyMetadata too (and DynamicResource too).
What am I missing?
UPDATE
This attached property works in other GridViews the only difference I can see here is that - I need to switch GridViews in the ListView which was not indicated above -but the key difference is that I inject as a style rather than directly. (On second thoughts this may not be the case since the firs item in a column in these other GridViews is always the largest item).
It was to do with the injection of a style.
I had two different GridViews switched by a boolean and used a DataTrigger to inject the alternate one and AutoSizeColumns worked fine on that altererate GridView! So I made both styles be injected by a DataTrigger rather having the default set by just a setter as in the XAML above.
As to why this is case I would be interested in finding out. At least I have a fully MVVM way of autosizing columns which I have not seen in other SO answers.
(BTW I have other attached properties that do not need me to manually trigger the autosize, I just wrote this version to identify the problem).