How to use DoubleToVisibilityConverter in uwp - c#

DoubleToVisibilityConverter can be used to easily change a double value to a Visibility based one based on a given threshold value. If both GreaterThan and LessThan are set, the converter will set the visibility if the target value is in-between those two values. Otherwise, it will look for the target being greater than or less than the specified value.
so my question is ----
How to use DoubleToVisibilityConverter for scrollviewer as i want to hide (back to Top)Button and scrollbar is at last down position and when scrollbar is going to up it will show the button.
i am using back to top button for scroll to top.
[ same as working in many website (scrollUp Button) ]
Below one is MainPage.xaml
XAML - Page Resource
<Page.Resources>
<converters:DoubleToVisibilityConverter x:Key="GreaterThanToleranceVisibilityConverter" GreaterThan="65.0"/>
</Page.Resources>
XAML - Ui Element
<Button x:name="Scroll_To_Up_Button" Visibility="{Binding ScrollableHeight, Converter{StaticResourceGreaterThanToleranceVisibilityConverter}/>
where i have to add
ScrollableHeight
in MainPage.xaml.cs and how to use in MyUWP App.

Did you try adding a parameter eg.
<Button x:name="Scroll_To_Up_Button" Visibility="{Binding ScrollableHeight, Converter={StaticResource GreaterThanToleranceVisibilityConverter}, ConverterParameter=42/>
Edit:
Give your page a name:
<Page Name="thisPage"
... >
Then point your binding source to it to access the dependency property ScrollableHeight if that is the type you declared it:
<Button x:name="Scroll_To_Up_Button" Visibility="{Binding ScrollableHeight, ElementName=thisPage, Converter={StaticResource GreaterThanToleranceVisibilityConverter}, ConverterParameter=42/>
And the dependency property in MainPage.xaml.cs:
public static readonly DependencyProperty ScrollableHeightProperty = DependencyProperty.Register(nameof(ScrollableHeight), typeof(int), typeof(*NAMEOFYOURPAGE*), new PropertyMetadata(null));
public int ScrollableHeight
{
get => (int)GetValue(ScrollableHeightProperty);
set => SetValue(ScrollableHeightProperty, value);
}

There is no need to write code in MainPage.xaml.cs for the binding of DoubleToVisibilityConverter. You could set the GreaterThan property or LessThan property of a DoubleToVisibilityConverter instance in MainPage.xaml file. And you should bind the ScrollViewer.VerticalOffset property which gets the distance the content has been scrolled vertically instead of ScrollViewer.ScrollableHeight property which is a constant if the content of the ScrollViewer is determined.
You could refer to the document for the usage of DoubleToVisibilityConverter.
Please check the following code as a sample:
<Page.Resources>
<converters:DoubleToVisibilityConverter x:Key="GreaterThanToleranceVisibilityConverter"
GreaterThan="100" TrueValue="Visible" FalseValue="Collapsed"/>
</Page.Resources>
<StackPanel>
<ScrollViewer x:Name="scrollViewer" Width="300" Height="300" VerticalScrollBarVisibility="Visible"
VerticalScrollMode="Enabled">
<TextBlock Text="111122222222222222222222222222222222211111111111111111111111111"
Width="10" TextWrapping="Wrap"
/>
</ScrollViewer>
<Button x:Name="Scroll_To_Up_Button" Content="back to up" Click="Scroll_To_Up_Button_Click" Margin="10"
Visibility="{Binding VerticalOffset,ElementName=scrollViewer,
Converter={StaticResource GreaterThanToleranceVisibilityConverter},FallbackValue=Collapsed,
TargetNullValue=Collapsed
}"/>
</StackPanel>

Related

How do I pass an input via binding to a custom property in UserControl? [duplicate]

I have a UserControl that I want to participate in data binding. I've set up the dependency properties in the user control, but can't get it work.
The uc displays the correct text when I call it with static text (e.g BlueText="ABC") . When i try to bind it to a local public property, it is always blank.
<src:BlueTextBox BlueText="Feeling blue" /> <!--OK-->
<src:BlueTextBox BlueText="{Binding Path=MyString}" /> <!--UserControl always BLANK!-->
<TextBox Text="{Binding Path=MyString}" Width="100"/> <!--Simple TextBox Binds OK-->
I've boiled the code down to the following simplified example. Here is the XAML of the UserControl:
<UserControl x:Class="Binding2.BlueTextBox" ...
<Grid>
<TextBox x:Name="myTextBox" Text="{Binding BlueText}" Foreground="Blue" Width="100" Height="26" />
</Grid>
Here is the code behind of the UserControl:
public partial class BlueTextBox : UserControl
{
public BlueTextBox()
{
InitializeComponent();
DataContext = this; // shouldn't do this - see solution
}
public static readonly DependencyProperty BlueTextProperty =
DependencyProperty.Register("BlueText", typeof(string), typeof(BlueTextBox));
public string BlueText
{
get { return GetValue(BlueTextProperty).ToString(); }
set { SetValue( BlueTextProperty, value.ToString() ); }
}
This seems like it should be really easy, but I can't make it work. Thanks for your help!
More info: When i was trying the fix suggested by Eugene, I noticed some peculiar behavior. I added a PropertyChangedCallback to the metadata; this allows me to watch the value of BlueText getting set. When setting the string to a static value (="feeling blue") the PropertyChanged event fires. The data binding case does not fire PropertyChanged. I think this means the data-bound value is not getting sent to the UserControl. (I think the constructor does not get called in the static case)
Solution: The problems were correctly identified by Arcturus and jpsstavares. First, I was overwriting the data binding when is set DataContext=this in the constructor of the control. This prevented the data bound value from getting set. I also had to name the control x:Name=root, and specify the Binding ElementName=root int the XAML. To get the TwoWay binding, I needed to set Mode=TwoWay in the caller. Here is the correct code:
<src:BlueTextBox BlueText="{Binding Path=MyString, Mode=TwoWay}}" /> <!--OK-->
Now the XAML in the UserControl:
<UserControl x:Class="Binding2.BlueTextBox" x:Name="root"...
<Grid>
<TextBox x:Name="myTextBox" Text="{Binding ElementName=root, Path=BlueText}" Foreground="Blue" Width="100" Height="26" />
</Grid>
Finally I removed the DataContext=this in the constructor of the UserControl.
public BlueTextBox()
{
InitializeComponent();
//DataContext = this; -- don't do this
}
Thanks everyone for the tremendous help!
You set the DataContext in the Control to itself, thus overwriting the DataContext when using this Control in other controls. Taking your binding as example in your situation:
<src:BlueTextBox BlueText="{Binding Path=MyString}" />
Once loaded and all the Datacontext is set, it will look for the path MyString in your BlueTextBox thing control due to you setting the DataContext to it. I guess this is not how you intended this to work ;).
Solution:
Change the text binding either one of the 2 bindings:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type src:BlueTextBox}}, Path=BlueText}
or
Name your control Root (or something like that)
<UserControl x:Name="Root"
{Binding ElementName=Root, Path=BlueText}
And remove the
DataContext = this;
from the constructor of your UserControl and it should work like a charm..
I think in this case you need to set the ElementName property in the binding. Something like this:
<UserControl x:Class="Binding2.BlueTextBox" x:Name="blueTextBox"...
<Grid>
<TextBox x:Name="myTextBox" Text="{Binding ElementName=blueTextBox, Path=BlueText}" Foreground="Blue" Width="100" Height="26" />
</Grid>
Possibly you need to add to your property FrameworkPropertyMetadata where specify FrameworkPropertyMetadataOptions.AffectsRender and AffectsMeasure.
FrameworkPropertyMetadataOptions enumeration MSDN article
I know this is an old topic but still.
Also mention the PropertyChangedCallback on the UIPropertyMetadata during registering your DP

Why does my dropdown feel so clunky?

I have a XAML UserControl embedded in a WinForms/WPF Interop ElementHost control. The control is pretty simple - it's just a dropdown with a button - here's the entire markup:
<UserControl x:Class="Rubberduck.UI.FindSymbol.FindSymbolControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Rubberduck.UI.FindSymbol"
mc:Ignorable="d"
d:DesignHeight="27" d:DesignWidth="270">
<UserControl.Resources>
<local:DeclarationImageConverter x:Key="DeclarationImageConverter" />
</UserControl.Resources>
<UserControl.CommandBindings>
<CommandBinding Command="local:FindSymbolControl.GoCommand"
Executed="CommandBinding_OnExecuted"
CanExecute="CommandBinding_OnCanExecute"/>
</UserControl.CommandBindings>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="32" />
</Grid.ColumnDefinitions>
<ComboBox IsEditable="True"
ItemsSource="{Binding MatchResults}"
SelectedItem="{Binding SelectedItem, UpdateSourceTrigger=PropertyChanged}"
Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}"
IsTextSearchCaseSensitive="False"
IsTextSearchEnabled="True"
TextSearch.TextPath="IdentifierName">
<ComboBox.ItemTemplate>
<DataTemplate DataType="local:SearchResult">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<Image Height="16" Width="16" Margin="2,0,2,0" Source="{Binding Declaration, Converter={StaticResource DeclarationImageConverter}}" />
<TextBlock Margin="2,0,2,0" Text="{Binding IdentifierName}" FontWeight="Bold" MinWidth="140" />
<TextBlock Margin="2,0,2,0" Text="{Binding Location}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Grid.Column="1"
Command="local:FindSymbolControl.GoCommand">
<Image Height="16" Source="pack://application:,,,/Rubberduck;component/Resources/arrow.png" />
</Button>
</Grid>
</UserControl>
The problem is that it doesn't work reliably, and far from instinctively.
If I type something in the box that actually matches an item, nothing happens until I manually select that item in the dropdown. Like here, I typed "sleepD", the box autocompleted to "sleepDelay", but the command is still disabled:
Once I've selected the item in the dropdown, the command button gets enabled as expected (although the image on the button doesn't show up grayed-out when the button is disabled, so it's not exactly as obvious as I intended it to be).
(the screenshot isn't really showing it, but there's only 1 match for that search)
If I click the button at that point, it works as expected. The problem is that if I make a new selection from the dropdown after that, the text box gets cleared instead of displaying the item I selected, and there's a weird delay during which the box is displaying what appears to be selected whitespace - this only seems to happen when the previous selection was made after selecting a value in the dropdown while the search text matches multiple entries, like "Sleep" above.
After the box got cleared, I can make a new selection from the dropdown and it will work as expected (except the VBE won't actually activate the CodePane I'm setting the selection to, but that's a separate issue).
The command implementation simply raises a Navigate event that passes a Declaration to the code that owns the VM instance.
The Search method, for which I need to add a .Take(50) after the .Select, to limit the number of returned results and perhaps reduce the lag a bit:
private void Search(string value)
{
var lower = value.ToLowerInvariant();
var results = _declarations.Where(
declaration => declaration.IdentifierName.ToLowerInvariant().Contains(lower))
.OrderBy(declaration => declaration.IdentifierName.ToLowerInvariant())
.Select(declaration => new SearchResult(declaration));
MatchResults = new ObservableCollection<SearchResult>(results);
}
private string _searchString;
public string SearchString
{
get { return _searchString; }
set
{
_searchString = value;
Search(value);
}
}
private SearchResult _selectedItem;
public SearchResult SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged();
}
}
private ObservableCollection<SearchResult> _matchResults;
public ObservableCollection<SearchResult> MatchResults
{
get { return _matchResults; }
set { _matchResults = value; OnPropertyChanged(); }
}
}
There's also an IValueConverter involved, that takes the Declaration in the SearchResult and switches on the declaration's DeclarationType enum to return a pack uri that points to the .png image to use in the dropdown list.
Aaah found it. It was all in the XAML.
Right here:
Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}"
That line doesn't belong there; binding the TextSearch.Text property instead...
TextSearch.Text="{Binding SearchString, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"
Makes it all work as intended. No glitch, no lag. Well there is a lag when I first drop the dropdown, but that's another issue.
Lesson learned: when TextSearch is enabled on an editable combobox, don't bind the Text property, unless you want weird behavior.

Forcing a ListBox to re-render

Background:
I have a ListBox containing items defined by DataTemplates. Right now, if an object in the list has the property IsEditable set to true, the item's property information will be displayed inside of textboxes (via DataTemplate change), instead of textblocks (so the user can edit the content of that list item)
IsEditable is toggled on/off by a button inside of each list item. I have been told that we need to keep the state of all objects consistent, which means I can't just rebind the ItemsSource and lose everything.
Currently, I'm using this to re-render:
this.lbPoints.Dispatcher.Invoke(DispatcherPriority.Render, new Action(() => { }));
Question:
The aforementioned code snippet KIND OF does its job. By "kind of", I mean, it does eventually cause my data to become re-rendered, but only when I scroll to the bottom of the list and then scroll back up to the item i'm trying to re-render.
1) How can I re-render the data immediately without having to scroll around to get it to show up?
The guys commenting are right that you're going about this the wrong way... there is rarely a need to force a ListBox to re-render. You're probably causing yourself some additional grief trying to switch the DataTemplates (although it is possible). Instead of that, think about data binding the TextBox.IsReadOnly property to your IsEditable property:
<TextBox IsReadOnly="{Binding IsEditable}" Text="{Binding Text}" />
Another alternative is to use a BooleanToVisibilityConverter to show a different Grid in your DataTemplate when your IsEditable property is true. Unfortunately, that Converter doesn't have an inverse operation, so you could create an IsNotEditing property to bind to the Grid in the DataTemplate that is originally displayed. I'm not sure if that's clear... see this example:
<DataTemplate DataType="{x:Type YourPrefix:YourDataType}">
<Grid>
<Grid Visibility="{Binding IsNotEditing, Converter={StaticResource
BooleanToVisibilityConverter}}">
<!-- Define your uneditable UI here -->
</Grid>
<Grid Visibility="{Binding IsEditing, Converter={StaticResource
BooleanToVisibilityConverter}}">
<!-- Define your editable UI here -->
</Grid>
</Grid>
</DataTemplate>
You could also define your own BooleanToVisibilityConverter class that has an IsInverted property, so that you can just use the one IsEditing property. You'd need to declare two Converters still, like this:
<Converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<Converters:BoolToVisibilityConverter x:Key="InvertedBoolToVisibilityConverter"
IsInverted="True" />
Then your XAML would be like this:
<Grid Visibility="{Binding IsEditing, Converter={StaticResource
InvertedBoolToVisibilityConverter}}">
<!-- Define your uneditable UI here -->
</Grid>
<Grid Visibility="{Binding IsEditing, Converter={StaticResource
BoolToVisibilityConverter}}">
<!-- Define your editable UI here -->
</Grid>

Data binding to a UserControl in WPF

I have a UserControl that I want to participate in data binding. I've set up the dependency properties in the user control, but can't get it work.
The uc displays the correct text when I call it with static text (e.g BlueText="ABC") . When i try to bind it to a local public property, it is always blank.
<src:BlueTextBox BlueText="Feeling blue" /> <!--OK-->
<src:BlueTextBox BlueText="{Binding Path=MyString}" /> <!--UserControl always BLANK!-->
<TextBox Text="{Binding Path=MyString}" Width="100"/> <!--Simple TextBox Binds OK-->
I've boiled the code down to the following simplified example. Here is the XAML of the UserControl:
<UserControl x:Class="Binding2.BlueTextBox" ...
<Grid>
<TextBox x:Name="myTextBox" Text="{Binding BlueText}" Foreground="Blue" Width="100" Height="26" />
</Grid>
Here is the code behind of the UserControl:
public partial class BlueTextBox : UserControl
{
public BlueTextBox()
{
InitializeComponent();
DataContext = this; // shouldn't do this - see solution
}
public static readonly DependencyProperty BlueTextProperty =
DependencyProperty.Register("BlueText", typeof(string), typeof(BlueTextBox));
public string BlueText
{
get { return GetValue(BlueTextProperty).ToString(); }
set { SetValue( BlueTextProperty, value.ToString() ); }
}
This seems like it should be really easy, but I can't make it work. Thanks for your help!
More info: When i was trying the fix suggested by Eugene, I noticed some peculiar behavior. I added a PropertyChangedCallback to the metadata; this allows me to watch the value of BlueText getting set. When setting the string to a static value (="feeling blue") the PropertyChanged event fires. The data binding case does not fire PropertyChanged. I think this means the data-bound value is not getting sent to the UserControl. (I think the constructor does not get called in the static case)
Solution: The problems were correctly identified by Arcturus and jpsstavares. First, I was overwriting the data binding when is set DataContext=this in the constructor of the control. This prevented the data bound value from getting set. I also had to name the control x:Name=root, and specify the Binding ElementName=root int the XAML. To get the TwoWay binding, I needed to set Mode=TwoWay in the caller. Here is the correct code:
<src:BlueTextBox BlueText="{Binding Path=MyString, Mode=TwoWay}}" /> <!--OK-->
Now the XAML in the UserControl:
<UserControl x:Class="Binding2.BlueTextBox" x:Name="root"...
<Grid>
<TextBox x:Name="myTextBox" Text="{Binding ElementName=root, Path=BlueText}" Foreground="Blue" Width="100" Height="26" />
</Grid>
Finally I removed the DataContext=this in the constructor of the UserControl.
public BlueTextBox()
{
InitializeComponent();
//DataContext = this; -- don't do this
}
Thanks everyone for the tremendous help!
You set the DataContext in the Control to itself, thus overwriting the DataContext when using this Control in other controls. Taking your binding as example in your situation:
<src:BlueTextBox BlueText="{Binding Path=MyString}" />
Once loaded and all the Datacontext is set, it will look for the path MyString in your BlueTextBox thing control due to you setting the DataContext to it. I guess this is not how you intended this to work ;).
Solution:
Change the text binding either one of the 2 bindings:
{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type src:BlueTextBox}}, Path=BlueText}
or
Name your control Root (or something like that)
<UserControl x:Name="Root"
{Binding ElementName=Root, Path=BlueText}
And remove the
DataContext = this;
from the constructor of your UserControl and it should work like a charm..
I think in this case you need to set the ElementName property in the binding. Something like this:
<UserControl x:Class="Binding2.BlueTextBox" x:Name="blueTextBox"...
<Grid>
<TextBox x:Name="myTextBox" Text="{Binding ElementName=blueTextBox, Path=BlueText}" Foreground="Blue" Width="100" Height="26" />
</Grid>
Possibly you need to add to your property FrameworkPropertyMetadata where specify FrameworkPropertyMetadataOptions.AffectsRender and AffectsMeasure.
FrameworkPropertyMetadataOptions enumeration MSDN article
I know this is an old topic but still.
Also mention the PropertyChangedCallback on the UIPropertyMetadata during registering your DP

How can I bind the nested viewmodels to properties of a control

I used Microsoft's Chart Control of the WPF toolkit to write my own chart control.
I blogged about it here. My Chart control stacks the yaxes in the chart on top of each other. As you can read in the article this all works quite well. Now I want to create a viewmodel that controls the data and axes in the chart. So far I'm able to add axes to the chart and show them in the chart. But I have a problem when I try to add the lineseries because it has one DependentAxis and one InDependentAxis property. I don't know how to assign the proper xAxis and yAxis controls to it.
Below you see part of the LineSeriesViewModel. It has a nested XAxisViewModel and YAxisViewModel property.
public class LineSeriesViewModel : ViewModelBase, IChartComponent
{
XAxisViewModel _xAxis;
public XAxisViewModel XAxis
{
get { return _xAxis; }
set
{
_xAxis = value;
RaisePropertyChanged(() => XAxis);
}
}
//The YAxis Property look the same
}
The viewmodels all have their own datatemplate.
The xaml code looks like this:
<UserControl.Resources>
<DataTemplate x:Key="xAxisTemplate" DataType="{x:Type l:YAxisViewModel}">
<chart:LinearAxis x:Name="yAxis" Orientation="Y" Location="Left" Minimum="0" Maximum="10" IsHitTestVisible="False" Width="50" />
</DataTemplate>
<DataTemplate x:Key="yAxisTemplate" DataType="{x:Type l:XAxisViewModel}">
<chart:LinearAxis x:Name="xAxis" Orientation="X" Location="Bottom" Minimum="0" Maximum="100" IsHitTestVisible="False" Height="50" />
</DataTemplate>
<DataTemplate DataType="{x:Type l:LineSeriesViewModel}">
<!--Binding doesn't work on the Dependent and IndependentAxis! -->
<!--YAxis XAxis and Series are properties of the LineSeriesViewModel -->
<l:FastLineSeries DependentAxis="{Binding Path=YAxis}"
IndependentAxis="{Binding Path=XAxis}"
ItemsSource="{Binding Path=Series}"/>
</DataTemplate>
<Style TargetType="ItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<!--My stacked chart control -->
<l:StackedPanel x:Name="stackedPanel" Width="Auto" Height="Auto" Background="LightBlue">
</l:StackedPanel>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ClipToBounds="True">
<!-- View is an ObservableCollection of all axes and series-->
<ItemsControl x:Name="chartItems" ItemsSource="{Binding Path=View}" Focusable="False">
</ItemsControl>
</Grid>
This code works quite well. When I add axes they get drawn. But the DependentAxis and InDependentAxis of the lineseries control stay null, so the series doesn't get drawn. How can I bind the nested viewmodels to the properties of a control?
It should work. A few things you can check:
Does the Series Binding work? If so, try to figure out what's the difference.
Are you sure that the XAxis and YAxis properties actually have values? Try putting a breakpoint in the getter. If it's reached, the Binding works. You can also put a converter (IValueConverter) on the Binding (that simply returns the value it receives) and place a breakpoint there.
Use PresentationTraceSources.TraceLevel=High on the Binding to get more verbose tracing (that will appear in the VS Output window).
Are DependentAxis/IndependentAxis defined as dependency properties on FastLineSeries?
Hope that helps,
Aelij.
You've probably already checked this but I find that when I'm debugging bindings the first and easiest place to start is running a debug session from VS as the debug output tells which objects and properties are failing to bind. I usually end up discovering I need to explicitly set a DataContext or something else like a typo. The output to look for start like this:
System.Windows.Data Error: 39 :
BindingExpression path error:
this is followed by the property name you tried to bind to and usually most importantly the class against which its actually trying to bind. If this doesn't help there's a great article here on the debugging bindings: http://www.beacosta.com/blog/?p=52 which discusses the use of PresentationTraceSources.TraceLevel=High which Aelij mentioned, as well as a few other techniques. Hope this gets onto the right track.
Regards,
Mike

Categories

Resources