I have an instance of a user control in my project that contains a slider. I would like to bind the ScaleTransform of a RichTextBox control to the value of the slider but I do not know how to properly reference it. The user control is called toolbar and the slider inside it is called Scale this is what I have tried so far:
<RichTextBox x:Name="body"
SelectionChanged="body_SelectionChanged"
SpellCheck.IsEnabled="True"
AcceptsReturn="True" AcceptsTab="True"
BorderThickness="0 2 0 0">
<RichTextBox.LayoutTransform>
<ScaleTransform ScaleX="{Binding ElementName=toolbar.Scale, Path=Value}" ScaleY="{Binding ElementName=toolbar.Scale, Path=Value}"/>
</RichTextBox.LayoutTransform>
</RichTextBox>
I have also tried doing it in the .cs file since I'm having trouble with the binding but haven't had any luck figuring out how to actually set the transform values once my slider event has been fired.
You cannot reference names inside other controls, they are in another name-scope. If you need the slider value bind it to a property of the UserControl.
UserControl code (whatever that class may be called in your case):
<!-- ToolBar.xaml -->
<UserControl ...
Name="control">
<!-- ... -->
<Slider Value="{Binding ScaleValue, ElementName=control}" ... />
<!-- ... -->
</UserControl>
// ToolBar.xaml.cs
public partial class ToolBar : UserControl
{
public static readonly DependencyProperty ScaleValueProperty =
DependencyProperty.Register("ScaleValue", typeof(double), typeof(ToolBar));
public double ScaleValue
{
{ get { return (double)GetValue(ScaleValueProperty); }
{ set { SetValue(ScaleValueProperty, value); }
}
}
New binding code:
<ScaleTransform ScaleX="{Binding ElementName=toolbar, Path=ScaleValue}" ... />
Related
We're in the process of converting a SL5+RIA service to a WPF front and WCF service as a temporary solution.
The old SL solution is maintained with VS 2015 14.0.25431.01. It uses SL5 and .NET 4.0. Other noteworthy libraries are Telerik SL controls 2011.2.0920 and Galasoft MVVMLight SL5 4.1.26.
The WPF solution is made using VS 2019 16.11.15, NET 4.8, Telerik WPF controls 2021.1.325 and Galasoft MVVMLight WPF45 4.1.26.
We have a custom control in which we use bars projected on a timescale to visualise timespans when items have different statusses through a period of time. It's a listbox of the different items and each line is filled with buttons to create the visual of a timebar accross a timeline.
To calculate the widths of the buttons, the total width in both timespan and pixels is used to calculate the comparative width in pixels for each button on each line.
Getting the total width in pixels is giving us problems.
The XAML Datatemplate of the control
<!--Timelines-->
<Grid Grid.Row="0" Grid.Column="1" x:Name="SomeGrid" Background="#FFD3D3D3">
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="10" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<!-- TEXTBLOCK TO TRIGGER HELPER CLASS ActualSizePropertyProxy -->
<TextBlock Visibility="Collapsed" x:Name="tb1" Text="{Binding ActualWidthValue, ElementName=proxy}" />
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" Text="{Binding ProgramViewmodel.TimelineAreaStart, Source={StaticResource Locator}, StringFormat=\{0:dd-MM-yyyy\}}" />
<TextBlock Grid.Row="0" Grid.Column="1" HorizontalAlignment="Right" Text="{Binding ProgramViewmodel.TimelineAreaEnd, Source={StaticResource Locator}, StringFormat=\{0:dd-MM-yyyy\}}" />
<!-- Vertical line under the start date -->
<TextBlock Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" Text="|" Margin="30 0 0 0" />
<ListBox x:Name="ListBoxTimeFreq"
Grid.Row="2"
Grid.Column="0"
Grid.ColumnSpan="2"
Background="#FFD3D3D3"
ItemsSource="{Binding Converter={StaticResource TimefrequentiesFromPAPConverter}}"
SelectionMode="Single"
ItemTemplate="{StaticResource TimeFreqTimelineItemTemplate}">
<ListBox.Resources>
<helpers:ActualSizePropertyProxy Element="{Binding ElementName=ListBoxTimeFreq}" x:Name="proxy" />
</ListBox.Resources>
</ListBox>
</Grid>
It's a simple grid showing the start date, end date and a | marking the start date. Then there is the listbox ListBoxTimeFreq below it that uses the TimeFreqTimelineItemTemplate datatemplate to fill in bars.
And above it all but collapsed there is a textblock that spreads itself across the available space for listbox items, across the available space for timelines. Whenever it's on screen, it triggers the getter on a property ActualWidthValue in a class ActualSizePropertyProxy.
The ActualSizePropertyProxy class
public class ActualSizePropertyProxy : FrameworkElement, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public FrameworkElement Element
{
get { return (FrameworkElement)GetValue(ElementProperty); }
set { SetValue(ElementProperty, value); }
}
public double ActualHeightValue
{
get { return Element == null ? 0 : Element.ActualHeight; }
}
public double ActualWidthValue
{
get
{
if (null != Element)
{
if (Element.ActualWidth != 0.0)
{
ViewModelLocator.ProgramViewmodel.PixelwidthTimelineArea = Element.ActualWidth - 90; // because of CbxValidateStatus, CbxEndOfLine & cbxAddScopeMultiplePAPs
ViewModelLocator.ProgramViewmodel.PixelwidthTimelineAreaScopes = Element.ActualWidth - 86; // because of the border
ViewModelLocator.ProgramViewmodel.Counter = 0;
ViewModelLocator.ProgramViewmodel.CounterScopes = 0;
}
return Element.ActualWidth;
}
else
{
ViewModelLocator.ProgramViewmodel.PixelwidthTimelineArea = 0;
ViewModelLocator.ProgramViewmodel.PixelwidthTimelineAreaScopes = 0;
return 0;
}
}
}
public static readonly DependencyProperty ElementProperty =
DependencyProperty.Register("Element", typeof(FrameworkElement), typeof(ActualSizePropertyProxy),
new PropertyMetadata(null, OnElementPropertyChanged));
private static void OnElementPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ActualSizePropertyProxy)d).OnElementChanged(e);
}
private void OnElementChanged(DependencyPropertyChangedEventArgs e)
{
FrameworkElement oldElement = (FrameworkElement)e.OldValue;
FrameworkElement newElement = (FrameworkElement)e.NewValue;
newElement.SizeChanged += new SizeChangedEventHandler(Element_SizeChanged);
if (oldElement != null)
{
oldElement.SizeChanged -= new SizeChangedEventHandler(Element_SizeChanged);
}
NotifyPropChange();
}
private void Element_SizeChanged(object sender, SizeChangedEventArgs e)
{
NotifyPropChange();
}
private void NotifyPropChange()
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ActualWidthValue"));
PropertyChanged(this, new PropertyChangedEventArgs("ActualHeightValue"));
}
}
}
The getter on ActualWidthValue reads out the ActualWidth of the element and stores the value in a viewmodel. Later having the width in that viewmodel is crucial because the converters calculating the sizes for the timebars use that value from the viewmodel.
In the old SL solution this setup causes the PixelwidthTimelineArea in the viewmodel to store a real value. When the application's width is around 1200, the ActualWidthValue return around 800.
But in the new WPF solution, the getter on ActualWidthValue is never triggered. None of the properties or events get triggered in ActualSizePropertyProxy because the binding goes wrong in the XAML in the WPF version.
And we can't figure out the right fix that is needed to make it work in WPF. It wants the source but you can't bind with Source when using ElementName.
Cannot find source for binding with reference 'ElementName=proxy'. BindingExpression:Path=ActualWidthValue; DataItem=null; target element is 'TextBlock' (Name='tb1'); target property is 'Text' (type 'String')
Cannot find source for binding with reference 'ElementName=ListBoxTimeFreq'. BindingExpression:(no path); DataItem=null; target element is 'ActualSizePropertyProxy' (Name='proxy'); target property is 'Element' (type 'FrameworkElement')
It's probably a very easy solution and our heads have gone mush trying to overthink it.
I have a large ListView which is largely made InkCanvas objects, it turns out that ListView implements data virtualisation to "cleverly" unload and load items in the view depending on the visible items in the view. The problem with this is that many times the ListView caches items and when a new item is added it essentially copy items already added in the view. So in my case, if the user adds a stroke to an Inkcanvas and then adds a new InkCanvas to the ListView, the new canvas contains the strokes from the previous canvas. As reported here this is because of the data virtualisation. My ListView is implemented as follows:
<Grid HorizontalAlignment="Stretch">
<ListView x:Name="CanvasListView" IsTapEnabled="False"
IsItemClickEnabled="False"
ScrollViewer.ZoomMode="Enabled"
ScrollViewer.HorizontalScrollMode="Enabled"
ScrollViewer.VerticalScrollMode="Enabled"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Visible"
HorizontalAlignment="Stretch">
<!-- Make sure that items are not clickable and centered-->
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<local:CanvasControl Margin="0 2"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
MinWidth="1000" MinHeight="100" MaxHeight="400"
Background="LightGreen"/>
<Grid HorizontalAlignment="Stretch" Background="Black" Height="2"></Grid>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
<InkToolbar x:Name="inkToolbar"
VerticalAlignment="Top"
Background="LightCoral"/>
<StackPanel HorizontalAlignment="Right">
<Button x:Name="AddButton" Content="Add Page" Click="Button_Click"/>
<TextBlock x:Name="PageCountText" />
</StackPanel>
</StackPanel>
</Grid>
A full example can be found here and here is a video of the issue.
Indeed if I turn off data virtualisation (or switch to an ItemsControl) everything works brilliantly. The problem however is that with a very large list, this approach has a heavy impact on performance (with 60+ InkCanvas controls the app just crashes). So is there a way to retain data virtualisation while avoiding the duplication of items? I have tried with VirtualizationMode.Standard but items are still duplicated.
To solve this problem, we must first understand why this problem occurs.
ListView has a reuse container inside, it will not endlessly create new list items, but will recycle.
In most cases, such recycling is not a problem. But it's special for InkCanvas.
InkCanvas is a stateful control. When you draw on InkCanvas, the handwriting is retained and displayed on the UI.
If your control is a TextBlock, this problem does not occur, because we can directly bind the value to TextBlock.Text, but for the Stroke of InkCanvas, we cannot directly bind, which will cause the so-called residue.
So in order to avoid this, we need to clear the state, that is, every time the InkCanvas is created or reloaded, the strokes in the InkCanvas are re-rendered.
1. Create a list for saving stroke information in ViewModel
public class ViewModel : INotifyPropertyChanged
{
// ... other code
public List<InkStroke> Strokes { get; set; }
public ViewModel()
{
Strokes = new List<InkStroke>();
}
}
2. Change the internal structure of CanvasControl
xaml
<Grid>
<InkCanvas x:Name="inkCanvas"
Margin="0 2"
MinWidth="1000"
MinHeight="300"
HorizontalAlignment="Stretch" >
</InkCanvas>
</Grid>
xaml.cs
public sealed partial class CanvasControl : UserControl
{
public CanvasControl()
{
this.InitializeComponent();
// Set supported inking device types.
inkCanvas.InkPresenter.InputDeviceTypes =
Windows.UI.Core.CoreInputDeviceTypes.Mouse |
Windows.UI.Core.CoreInputDeviceTypes.Pen;
}
private void StrokesCollected(InkPresenter sender, InkStrokesCollectedEventArgs args)
{
if (Data != null)
{
var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes().ToList();
Data.Strokes = strokes.Select(p => p.Clone()).ToList();
}
}
public ViewModel Data
{
get { return (ViewModel)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(ViewModel), typeof(CanvasControl), new PropertyMetadata(null,new PropertyChangedCallback(Data_Changed)));
private static void Data_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(e.NewValue!=null && e.NewValue is ViewModel vm)
{
var strokes = vm.Strokes.Select(p=>p.Clone());
var instance = d as CanvasControl;
instance.inkCanvas.InkPresenter.StrokesCollected -= instance.StrokesCollected;
instance.inkCanvas.InkPresenter.StrokeContainer.Clear();
try
{
instance.inkCanvas.InkPresenter.StrokeContainer.AddStrokes(strokes);
}
catch (Exception)
{
}
instance.inkCanvas.InkPresenter.StrokesCollected += instance.StrokesCollected;
}
}
}
In this way, we can keep our entries stable.
I have a UserControl (let's call it "CustomControl") that acts as a simple toolbar for a grid. It contains only a button to open/close (it just changes it's visibility property) a Grid.
Like this:
<UserControl ....>
...
<Grid>
<Button x:Name="btChangeState" Content="Change State" />
</Grid>
....
</UserControl>
And I have declared the DependencyProperty of a Grid like the following:
public Grid MyContent
{
get { return (Grid)GetValue(MyContentProperty); }
set { SetValue(MyContentProperty, value); }
}
public static readonly DependencyProperty MyContentProperty =
DependencyProperty.Register("MyContent", typeof(Grid), typeof(MyControl), new PropertyMetadata(null));
And my user control button has a simple handler to change the "MyContent" Grid Visible or collapsed:
if (MyContent.Visibility == Windows.UI.Xaml.Visibility.Visible)
MyContent.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
else
MyContent.Visibility = Windows.UI.Xaml.Visibility.Visible;
On my ViewPage, I am able to wire up the theUserControl.MyContent to theGrid in the constructor of the Page, but how can I do that in the XAML? I would like to do something like this:
<Page .....>
<StackPanel Orientation="Vertical">
<cs:CustomControl x:Name="theUserControl" MyContent="{Binding theGrid}" />
<Grid x:Name="theGrid" Height="200" Width="200" Background="Red" />
</StackPanel>
</Page>
Is it possible?
You need to use ElementName:
<cs:CustomControl x:Name="theUserControl" MyContent="{Binding ElementName=theGrid}" />
I'm trying to figure out how to change a control's template to something that will make it held inside a Grid, like this:
<ControlTemplate x:Key="containedTemplate">
<Grid>
<!-- place templated control here -->
</Grid>
</ControlTemplate>
I of course want any of the inner control's properties to be synced automatically with the templated control.
Can this be done at all?
Here's an hypothetical example for a TextBox template:
<ControlTemplate x:Key="textTemplate" TargetType="{x:Type TextBox}">
<Grid Background="Red">
<TextBox Name="InnerTextBox" Margin="5,5,5,5"/>
</Grid>
</ControlTemplate>
Now if I did apply the template on a TextBox instance like this:
<TextBox Text="{Binding MyTextProperty}" Template="{StaticResource textTemplate}"/>
... then the control would magically be a Grid, containing a TextBox with a few margins and whose Text's property would be bound to MyTextProperty of whatever DataContext instance has been set:
<!-- runtime visual tree I'd like to be produced by the above XAML -->
<Grid Background="Red">
<TextBox Text="{Binding MyTextProperty}" Margin="5,5,5,5"/>
</Grid>
If I had the following code:
<StackPanel>
<TextBox Text="{Binding MyTextProperty}" Template="{StaticResource textTemplate}"/>
<TextBox Text="{Binding MyOtherTextProperty}" Template="{StaticResource textTemplate}"/>
<TextBox Text="{Binding YetAnotherTextProperty}" Template="{StaticResource textTemplate}"/>
</StackPanel>
The resulting tree would be this:
<!-- runtime visual tree I'd like to be produced by the above XAML -->
<StackPanel>
<Grid Background="Red">
<TextBox Text="{Binding MyTextProperty}" Margin="5,5,5,5"/>
</Grid>
<Grid Background="Red">
<TextBox Text="{Binding MyOtherTextProperty}" Margin="5,5,5,5"/>
</Grid>
<Grid Background="Red">
<TextBox Text="{Binding YetAnotherTextProperty}" Margin="5,5,5,5"/>
</Grid>
</StackPanel>
In these examples you can see that the TextBox's Text property is correctly propagated down to the "inner" TextBox instance. The control's default visual tree is also preserved (borders, typing area, etc.).
I'm aware of template parts but as I said I'm trying to find a global approach here, and I DO NOT want to change the control's appearance; only put it inside a container.
frankly, this question exhausted me, i have this only answer but not convince me a lot.
first you should create multi ControlTemplates for each control that you want to set your template then create this class
public class ControlTemplateConverter
{
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(ControlTemplateConverter), new UIPropertyMetadata(false, IsEnabledChanged));
private static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
ControlTemplate t;
if (d == null) return;
if (d is TextBlock)
t = App.Current.FindResource("TextBoxTemplate") as ControlTemplate;
else if (d is CheckBox)
t = App.Current.FindResource("CheckBoxTemplate") as ControlTemplate;
// and So On
(d as Control).Template = t;
}
public static bool GetIsEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsEnabledProperty);
}
public static void SetIsEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsEnabledProperty, value);
}
}
and your control should like this:
<TextBox local:ControlTemplateConverter.IsEnabled="True"></TextBox>
<CheckBox local:ControlTemplateConverter.IsEnabled="True"></CheckBox>
I am working on creating a tag cloud in Silverlight 2 and trying to bind data from a List collection to a Scale transform on a TextBlock. When running this I get an AG_E_PARSER_BAD_PROPERTY_VALUE error. Is it possible to data bind values to transforms in Silverlight 2? If not could I do something to the effect of FontSize={Binding Weight*18} to multiply the tag's weight by a base font size? I know this won't work, but what is the best way to calculate property values for items in a DataTemplate?
<DataTemplate>
<TextBlock HorizontalAlignment="Stretch" VerticalAlignment="Stretch" TextWrapping="Wrap" d:IsStaticText="False" Text="{Binding Path=Text}" Foreground="#FF1151A8" FontSize="18" UseLayoutRounding="False" Margin="4,4,4,4" RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="{Binding Path=WeightPlusOne}" ScaleY="{Binding Path=WeightPlusOne}"/>
</TransformGroup>
</TextBlock.RenderTransform>
The issue seems to be Rule #1 from this post:
The target of data binding must be a FrameworkElement.
So since ScaleTransform isn't a FrameworkElement it doesn't support binding. I tried to bind to a SolidColorBrush to test this out and got the same error as with the ScaleTransform.
So in order to get around this you can create a control that exposes a dependency property of your tag data type. Then have a property changed event that binds the properties of your tag data to the properties in the control (one of which would be the scale transform). Here is the code I used to test this out.
items control:
<ItemsControl x:Name="items">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:TagControl TagData="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
tag control xaml:
<UserControl x:Class="SilverlightTesting.TagControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<TextBlock x:Name="text" TextWrapping="Wrap" FontSize="18" Margin="4,4,4,4">
<TextBlock.RenderTransform>
<ScaleTransform x:Name="scaleTx" />
</TextBlock.RenderTransform>
</TextBlock>
</UserControl>
tag control code:
public partial class TagControl : UserControl
{
public TagControl()
{
InitializeComponent();
}
public Tag TagData
{
get { return (Tag)GetValue(TagDataProperty); }
set { SetValue(TagDataProperty, value); }
}
// Using a DependencyProperty as the backing store for TagData. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TagDataProperty =
DependencyProperty.Register("TagData", typeof(Tag), typeof(TagControl), new PropertyMetadata(new PropertyChangedCallback(TagControl.OnTagDataPropertyChanged)));
public static void OnTagDataPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var tc = obj as TagControl;
if (tc != null) tc.UpdateTagData();
}
public void UpdateTagData()
{
text.Text = TagData.Title;
scaleTx.ScaleX = scaleTx.ScaleY = TagData.Weight;
this.InvalidateMeasure();
}
}
Seems like overkill for just setting a single property, but I couldn't find an easier way.