Re-render a usercontrol after removing it from a container - c#

I have UserControl which I display like this, it's for previewing a receipt ticket before printing:
<ScrollViewer x:Name="xzScroll" Template="{StaticResource scrollView}" Height="488" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch">
<Border x:Name="xzPreview" BorderThickness="0" Background="White" Margin="0,0,10,0" Padding="15">
// The UserControl
</Border>
</ScrollViewer>
When the user wants to print the receipt, I remove the UserControl from its parent Border, and try to re-measure and re-arrange the control according to the printeable area:
public void Print()
{
PrintDialog dlg = new PrintDialog();
this.Measure(new Size(dlg.PrintableAreaWidth, double.PositiveInfinity));
this.Arrange(new Rect(this.DesiredSize));
this.UpdateLayout();
dlg.PrintTicket.PageMediaSize = new PageMediaSize(this.ActualWidth, this.ActualHeight);
....
}
However, the Height and Width properties won't change, resulting in a faulty print with messed up margins and some text clipping on the sides.
When I create a new instance of my UserControl in code, and then use the Measure and Arrange methods, I see the Width and Height properties change according to the PrinteableAreaWidth, resulting in a proper receipt ticket.
How can I make sure the Width and Height properties are properly getting set?

Is it right that you want to to resize the Usercontrol?
If yes, why don't create a Class (ViewModel) for the Usercontrol with the two Properties Height and Width and bind the User Control to these Properties.
Than you can change the size without removing the Usercontrol.
Here for Example how such a ViewModel could look like. You will need to implement the INotifyPropertyChanged.
public class UserControlViewModel:INotifyPropertyChanged
{
private double _height;
private double _width;
public double _height
{
get { return _height;}
set {
if(_height == value)
return;
_height = value;
OnPropertyChanged();
}
public double _width
{
get { return _width;}
set {
if(_width == value)
return;
_width = value;
OnPropertyChanged();
}
And in Xaml you can bind it this way.
<ScrollViewer x:Name="xzScroll" Template="{StaticResource scrollView}" Height="488" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch">
<Border x:Name="xzPreview" BorderThickness="0" Background="White" Margin="0,0,10,0" Padding="15">
<MyUserControl
DataContext = "{Binding MyControlViewModelObject}"
Height = "{Binding Height}"
Width = "{Binding Width}"/>
</Border>
</ScrollViewer>

It is possible that you have declared the UI elements in your UserControl with exact Margin and/or Height and Width dimensions... of course, I can't be sure about that because you didn't show us your relevant code.
However, if this is the case, then you won't be able to rearrange them by re-sizing the UserControl externally. The solution is to use Grid controls to organise your UI elements, so that they can be automatically re-sized when the UserControl is resized. See the Grid Class page on MSDN for further information.

Related

Get the ActualWidth of a user control

I want to populate a Vertical StackPanel with objects of another UserControl. All UserControls should be scaled by the widest one. The widest control should use the full available width.
ParentControl
<Grid x:Name="mainGrid" >
<StackPanel x:Name="myStackPanel" />
</Grid>
private void OnMyStackPanel_Loaded(object sender, RoutedEventArgs e)
{
myStackPanel.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
myStackPanel.Arrange(new Rect(0, 0, myStackPanel.DesiredSize.Width, myStackpanel.DesiredSize.Height));
var width = myStackPanel.ActualWidth;
}
The problem is that the actual width after is 0. I have to add an element to the StackPanel. Is there a way to wrap this into something that asumes the available width so I can read that? Using a Dispatcher is not working. The ParentControl is nested into a Grid Cell of a View.

How do I add a Vertical Scrollbar to Telerik RadLegendControl within RadCartesianChart?

I am developing a program within UWP that will have the ability to chart data on-screen, and must support the potential that a user may want to chart a large number of series on it.
The Chart that I am using is Telerik's RadCartesianChart and I am using a RadLegendControl for showing the legend for the chart. This is laid out on a Grid that has two Columns and one Row. In the first Column (0) is the RadLegendControl. In the second Column (1) is the RadCartesianChart.
When a large number of Series are drawn, this can result in the Legend going down below the bottom of the app, cutting off the remaining items in the legend. This is basically an "excessive" example of the usage of this chart and I'm wanting to make sure it can function effectively when put under this kind of use.
Is there a way to add a scrollbar to the Legend Control so that a user can scroll through the legend? Or should I be looking at a different method for showing the legend?
This is for a program made within UWP that is currently targeting a minimum version of Windows 10 1803 and aiming for 1809, using Visual Studio 2019.
I made a post over on Telerik's forum asking this question and it was suggested that there is possibly an external component that is letting the legend extend to its full height off-screen and they provided a possible solution in trying to set an explicit maximum height to see the scrollbar appear when it reaches that upper bound. As such, in the XAML I set MaxHeight="300", which is much smaller than the average Chart's legend would require, such that I could easily see if the Scrollbar appeared. When I tried this, no Scrollbar appeared.
Originally I was having the RadLegendControl being drawn utilising a StackPanel to reorder the Legend to display from top-down instead of left-to-right so that it could fit alongside the Chart. I suspected that the StackPanel's internal ScrollViewer may have been conflicting with the RadLegendControl's internal ScrollViewer. I removed the StackPanel layout to ensure that it could not conflict to see if a ScrollViewer would then appear. It did not (I tested a horizontal one as well, to no success).
I have tried other solutions such as binding the MaxHeight property of the RadLegendControl to the Height or ActualHeight of the Grid row that it is on, explicitly setting VerticalScrollMode to Enabled and VerticalScrollVisibility to Visible.
This is the RadLegendControl code within XAML, still with the MaxHeight set explicitly to 300:
<telerikPrimitives:RadLegendControl
x:Name="LegendForChart"
LegendProvider="{Binding ElementName=MainChart}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Top"
MaxHeight="300"
>
<telerikPrimitives:RadLegendControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</telerikPrimitives:RadLegendControl.ItemsPanel>
</telerikPrimitives:RadLegendControl>
Where telerikPrimitives is defined with the following:
xmlns:telerikPrimitives="using:Telerik.UI.Xaml.Controls.Primitives"
I have tried adding/modifying the following lines:
MaxHeight="{Binding ElementName=ChartGrid, Path=Height, Mode=Oneway}"
MaxHeight="{Binding ElementName=ChartGrid, Path=ActualHeight, Mode=Oneway}"
ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollMode="Enabled"
Currently with my file that can display ~332 Series on my Chart, the Legend does not have a Scrollbar showing, with the items disappearing off-screen. (Unfortunately I don't have enough rep to show an image).
I would like to find a solution where, if there are sufficient Series showing, a vertical Scrollbar would appear and allow the user to scroll down through the Legend.
I realise this may appear as excessive, but I would like to ensure that my program behaves appropriately if a user would, for any reason, decide to display a large number of Series on the chart.
Since you just provide the RadLegendControl XAML code, I did not see your whole XAML code sample. I'm not sure what the issue is on your side.
So, I made a simple code sample according to the Telerik's official document.
I just use a ScrollViewer control to wrap the RadLegendControl, then it will be scrollable.
Please see my code sample:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"></ColumnDefinition>
<ColumnDefinition Width="8*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<telerikChart:RadCartesianChart x:Name="chart" Grid.Column="1">
<telerikChart:RadCartesianChart.HorizontalAxis>
<telerikChart:CategoricalAxis />
</telerikChart:RadCartesianChart.HorizontalAxis>
<telerikChart:RadCartesianChart.VerticalAxis>
<telerikChart:LinearAxis />
</telerikChart:RadCartesianChart.VerticalAxis>
<telerikChart:RadCartesianChart.SeriesProvider>
<telerikChart:ChartSeriesProvider x:Name="provider">
<telerikChart:ChartSeriesProvider.SeriesDescriptors>
<telerikChart:CategoricalSeriesDescriptor ItemsSourcePath="GetData" ValuePath="Value" CategoryPath="Category" LegendTitlePath="LegendTitle">
<telerikChart:CategoricalSeriesDescriptor.Style>
<Style TargetType="telerikChart:BarSeries">
<Setter Property="CombineMode" Value="Cluster" />
</Style>
</telerikChart:CategoricalSeriesDescriptor.Style>
</telerikChart:CategoricalSeriesDescriptor>
</telerikChart:ChartSeriesProvider.SeriesDescriptors>
</telerikChart:ChartSeriesProvider>
</telerikChart:RadCartesianChart.SeriesProvider>
</telerikChart:RadCartesianChart>
<ScrollViewer>
<telerikPrimitives:RadLegendControl x:Name="LegendForChart" LegendProvider="{Binding ElementName=chart}">
<telerikPrimitives:RadLegendControl.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</telerikPrimitives:RadLegendControl.ItemsPanel>
</telerikPrimitives:RadLegendControl>
</ScrollViewer>
</Grid>
private Random r = new Random();
public List<ViewModel> GenerateCollection()
{
List<ViewModel> collection = new List<ViewModel>();
for (int i = 0; i < 500; i++)
{
ViewModel vm = new ViewModel();
vm.GetData = GenerateData();
vm.LegendTitle = "ViewModel " + i;
collection.Add(vm);
}
return collection;
}
public List<Data> GenerateData()
{
List<Data> data = new List<Data>();
data.Add(new Data { Category = "Apple", Value = r.Next(1, 20) });
data.Add(new Data { Category = "Orange", Value = r.Next(10, 30) });
data.Add(new Data { Category = "Lemon", Value = r.Next(20, 40) });
return data;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
this.provider.Source = GenerateCollection();
}
public class ViewModel
{
public List<Data> GetData { get; set; }
public string LegendTitle { get; set; }
}
public class Data
{
public double Value { get; set; }
public string Category { get; set; }
}

How can I make a multi-colored segmented progress bar in wpf?

I'm trying to create a UserControl that acts as a sort of segmented progress bar. Input would be a collection of objects, each object would have a category, a duration property, and status property. The UserControl should stretch the width and height of the parent control. Each item in the collection should represent a segment of the progress bar; color of the segment is related to the status, the width of the segment is related to the duration, and the text overlaid on the segment would be related to the category or something.
Example custom progress bar:
The text might be the collection item's ID, the top segment color would be related to status, the bottom color would be related to the category, and the width related to the duration.
Some of the options I've considered:
Make a stackpanel and somehow define each items width and wrap the whole thing in a viewbox to make it stretch the height and width. How could I control the text size, how do I make the content fit the height, how do I bind a stackpanel to a collection?
Make an attached property for a grid control that would dynamically create columns and map the collection items to the grids. Seems like a lot of work and I'm hoping theres a simpler solution since my requirements are pretty specific.
Maybe theres a way to override a uniform grid to make it non-uniform?
Maybe I should just go all code-behind and draw rectangles by iterating through my collection?
Either way, I am crossing my fingers that somebody might know a simple solution to my problem.
Here is a full working proposition of solution to the custom progress bar.
Code is here : http://1drv.ms/1QmAVuZ
1 . If all the steps are not the same width, I prefer to use Grid with columns and different widths
The columns are built dynamically based upon following class :
public class StepItem
{
public int Length { get; set; }
public int Index { get; set; }
public String Label { get; set; }
public Brush Brush { get; set; }
}
2. I chose to implement a CustomControl and inherit of ItemsControl
CustomControl because I don't want to take care of implementing of the parts of the template of the Progressbar.
ItemsControl because :
-I want to provide to ItemsSource property a collection of StepItems
-ItemsControl can have some DataTemplate as template for each item
-ItemsControl can have any Panel like Grid as template presenting the collection of items
3. The component has template in Generic.xaml
-layoutGrid wil have the "continuous rainbow"
-overlayGrid will be displayed partially over the steps depending on progression or totally over (if no progress)
-ItemsPresenter will present the collection of DataTemplates corresponding to each StepItem
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ProgressItemsControl}">
<Border BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid x:Name="layoutGrid">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<Grid x:Name="overlayGrid" Width="100" HorizontalAlignment="Right" Background="White"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
4. Customisation of the ItemsPanel to use a Grid (instead of vertical layout)
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate >
<Grid x:Name="stepsGrid" IsItemsHost="True" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
5. In code behind of components, setting of the column width
int i = 0;
foreach (StepItem stepItem in ItemsSource)
{
total += stepItem.Length;
var columnDefinition = new ColumnDefinition() { Width = new GridLength(stepItem.Length, GridUnitType.Star) };
stepsGrid.ColumnDefinitions.Add(columnDefinition);
Grid.SetColumn(stepsGrid.Children[i], stepItem.Index);
i++;
}
6. Code behind for declaring Dependency properties that can be monitored
(excerpt)
public int Value
{
get { return (int)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(int), typeof(ProgressItemsControl), new PropertyMetadata(0));
7. Usage of the component
<local:CustomProgressBar
x:Name="customProgressBar1"
HorizontalAlignment="Left" Height="50" Margin="32,49,0,0"
VerticalAlignment="Top" Width="379"/>
8. Feeding the component with data
private List<StepItem> stepItems = new List<StepItem>{
new StepItem{
Index=0,
Label="Step1",
Length=20,
Brush = new SolidColorBrush(Color.FromArgb(255,255,0,0)),
new StepItem{
Index=4,
Label="Step5",
Length=25,
Brush = new SolidColorBrush(Color.FromArgb(255,0,128,0)),
},
};
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
progressItemsControl1.ItemsSource = stepItems;
}
Regards

Windows Phone - How to draw shapes in dynamically created canvas - XAML vs code behind

I have a longListSelector that create several canvas dynamically and I want to draw in each canvas by using data from my ObservableCollection Games.
Here is my base code of the main page:
<Grid x:Name="ContentPanel">
<phone:LongListSelector Name="myLLS" ItemSource="{Binding GamesVM}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel>
<Canvas /> <!-- Here I want to draw -->
<TextBlock Text="{Binding Title}"/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
public class GameVM : INotifyPropertyChanged {
private string _title;
public string Title {
get { return this._title; }
set {
if (this._title!= value) {
this._title= value;
this.OnPropertyChanged("Title");
}
}
}
public void Draw() {
Ellispe stone = new Ellipse();
// [...] Add Fill, Strock, Width, Height properties and set Canvas.Left and Canvas.Top...
myCanvas.Children.Add(stone);
}
}
I would like to execute my Draw method when my GamesVM collection is generated but I haven't access to the corresponding canvas at this time. Putting my Draw method in code behind doesn't help because I have no event to handle where I could get both data binding object and the canvas newly generated (except if I miss something...). So I have no "myCanvas" instance in my Draw method.
I have some ideas to do that but nothing work well.
Option 1
I can put my UIElement (Ellipse, Line, etc) in an ObservableCollection which is binded in an ItemsControl like this :
<ItemsControl ItemsSource="{Binding myUIElements}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
public void Draw() {
myUIElements = new ObservableCollection<UIElement>();
Ellispe stone = new Ellipse();
// [...] Add Fill, Strock, Width, Height properties and set Canvas.Left and Canvas.Top...
myUIElements.Add(stone);
}
It works but when I leave the page and come back, I get an Element is already the child of another element exception.
If I use VisualTreeHelper to find my ItemsControl and call Items.Clear() on it, I get an exception too beacuse Items is read-only.
Option 2
I can use a ContentControl instead of ItemsControl and create the canvas in my Draw method:
<ContentControl Content="{Binding myUICanvas"/>
public void Draw() {
myUICanvas = new Canvas();
Ellispe stone = new Ellipse();
// [...] Add Fill, Strock, Width, Height properties and set Canvas.Left and Canvas.Top...
myUICanvas.Children.Add(stone);
}
It works too but when I leave the page and come back, I get a Value does not fall within the expected range exception.
I understand that I can't bind UIElement because I can't clear them when the Framework try to set them again. What is the trick to say "Please, do not add the same element twice" ?
Option 3
I can try to draw directly in XAML and bind a ViewModel object instead of UIElement object.
<ItemsControl ItemsSource="{Binding myDatas}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Width="{Binding Diameter}" Fill="Black" ...>
</Ellipse>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It could work in WPF but in my Windows Phone 8 app, I have no ItemContainerStyle property to set Canvas.Left and Canvas.Right. Beside I would have to use a CompositeCollection to deal with several kind of shapes but DataType is not recognized by Visual Studio.
Moreover, even if it works with Line UIElements, the render is slower than c# approach.
So, what is the best option and how to deal with my exceptions ?
For information, I give you which one I choose.
I take option 2 and avoid the come back error by redrawing a new Canvas each time. I change my Draw definition so it return me the new Canvas.
public class GameVM : INotifyPropertyChanged {
// Title and other properties
private Canvas _myUICanvas;
public Canvas myUICanvas
{
get {
_myUICanvas = Draw();
return _myUICanvas;
}
set {
// this is never called
_myUICanvas = value;
}
}
public Canvas Draw() {
Canvas newCanvas = new Canvas();
Ellispe stone = new Ellipse();
// [...] Add Fill, Strock, Width, Height properties and set Canvas.Left and Canvas.Top...
newCanvas.Children.Add(stone);
return newCanvas;
}
}
Like this, I can run my program without error and without reloading/recreating all the GameVM instances.

Binding to TextBlock Width property in MVVM

I have a TextBlock control in my view, its Width depends on the Text property.
I'm looking for some way to bind the TextBlocks Width to a property in my model,which will work as follows:
The setting of the Width must be done automatically based on Text
In my button click I would like to retrieve the Width
I've tried the code below, but it keeps the Width as 0 if I don't explicitly set it in the constructor of the view model.Tried Mode=OneWayToSource and Mode=OneWay but it made no difference, any suggestions?
View:
<Grid>
<TextBlock Text="Some text" Width="{Binding TextWidth,Mode=OneWayToSource}" />
<Button Content="Show Width" Height="30" Width="90" Command="{Binding ShowTextWidth}" />
</Grid>
View model:
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private DelegateCommand<object> showTextWidth;
public DelegateCommand<object> ShowTextWidth
{
get { return showTextWidth; }
set { showTextWidth = value; }
}
private double textWidth;
public double TextWidth
{
get { return textWidth; }
set
{
textWidth = value;
OnPropertyChanged("TextWidth");
}
}
public ViewModel()
{
//If I explicitly specify the width it works:
//TextWidth = 100;
ShowTextWidth = new DelegateCommand<object>(ShowWidth);
}
private void ShowWidth(object parameter)
{
MessageBox.Show(TextWidth.ToString());
}
}
Ended up creating an attached behavior by Maleak which was inspired by Kent Boogaarts Pushing read-only GUI properties back into ViewModel, can't believe it's so complicated to push the value of ActualWidth into the view model!
Width is a DependencyProperty on TextBlock. In this case it's a Target for Binding and TextWidth is your source for Binding. OneWayToSource seems like the way to go, you are setting TextWidth to 100 in the ViewModel which does not propogate to Width on TextBlock because it's OneWayToSource yes correct, Width (Target) is then setting TextWidth (Source) to Double.NaN because of OneWayToSource and that's why you're seeing 0...
ActualWidth should work like sa_ddam213 said but also consider that your TextBlock doesn't grow in Width (ActualWidth) when the Text changes because it is spanning the total width of your Grid layout. Either put it in a ColumnDefinition with Width set to Auto or make your HorizontalAlignment Left to see the ActualWidth change when the Text changes.
I have made some changes to your source code. Consider using CommandParameter on your button? Check out the link...
WpfApplication10.zip
you dont really need to set the Width if you choose a layout panel wich can handle this for you.
eg. a columndefinition with width=auto grows with your text
To set the Width depending on its containing Text you could bind the Width-Property to the Text-Property and use a converter like so:
<TextBlock Width="{Binding RelativeSource={RelativeSource Self}, Path=Text, Converter={StaticResource TextToWidth}}"
Your converter could look like:
TextBlock tb = new TextBlock();
tb.Text = value.ToString();
tb.Measure(new Size(int.MaxValue, 20)); //20 if there shouldnt be a linebreak
return tb.DesiredSize.Width;

Categories

Resources