WPF dynamically create button by style and set control elements inside - wpf

We have a style defined as follow:
<Style x:Key="StartButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Button ... Style="{StaticResource StartBtnStyle}">
<Button.Content>
<StackPanel>
<TextBlock x:Name="Line1" Text="..." FontSize="20" />
<TextBlock x:Name="Line2" Text="..." FontSize="8" />
</StackPanel>
</Button.Content>
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
We creates a button dynamically:
var button = new Button() {
Margin = new Thickness(3d,3d,3d,10d),
Style = FindResource("StartButtonStyle") as Style,
};
We want to find the "Line1" textblock inside the new button, and set the Text property:
var line1 = (TextBlock)button.FindName("Line1");
But it finds only "null" :( How should we find the textblock inside the button? Any advice is appreciated! Thanks in advance!

Wait until the Style has been applied - there is no TextBlock elment before this - to the Button and then find the TextBlock in the visual tree:
var button = new Button()
{
Margin = new Thickness(3d, 3d, 3d, 10d),
Style = FindResource("StartButtonStyle") as Style,
};
button.Loaded += (s, e) =>
{
TextBlock line1 = FindChild<TextBlock>(button, "Line1");
if(line1 != null)
{
line1.Text = "...";
}
};
The recursive FindChild<T> method is from here.

Related

How to Get contextmenu object in opening event

When contextmenu is opening, I want to fill a textbox with source control's information, for example its name and so on, for viewing and editing purpose.
But I however cannot access the opening contextmenu anyway.
Maybe this is because my less understanding of control's xaml style.
The following is my xaml:
the context menu part: I want to fill the TextBox when contextmenu is opening.
<ContextMenu x:Key="VacUnitContextMenu" >
<MenuItem Header="Property">
<MenuItem>
<MenuItem.Header>
<StackPanel Orientation="Horizontal">
<Label Content="Name" />
<TextBox Width="60" Name="VacName" Margin="2,0" />
</StackPanel>
</MenuItem.Header>
</MenuItem>
<MenuItem Header="Get" Command="{x:Static s:DesignerCanvas.VacUnitNameGet}"/>
</MenuItem>
<MenuItem Header="X">
<MenuItem Header="TEST" Command="{x:Static s:DesignerCanvas.XTest}">
<MenuItem.Icon>
<Image Source="Images/SendToBack.png" Width="16"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</ContextMenu>
the context menu is used in this way:
<!-- VacUnit Style -->
<Style TargetType="{x:Type s:VacUnit}" >
<Setter Property="MinWidth" Value="10"/>
<Setter Property="MinHeight" Value="10"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type s:VacUnit}">
<Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
ContextMenu="{StaticResource VacUnitContextMenu}"
>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I use conextmenuopening event, and try to fill the textbox there,
How to: Handle the ContextMenuOpening Event
But I cannnot get the contextmenu, it is null:
FrameworkElement fe = e.Source as FrameworkElement;
ContextMenu cm = fe.ContextMenu;
Many thanks in advance.
Ting
Getting a reference to the ContextMenu should be easy provided that you handle the ContextMenuOpening event for the Grid in the ControlTemplate:
<ControlTemplate TargetType="{x:Type s:VacUnit}">
<Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}"
ContextMenu="{StaticResource VacUnitContextMenu}"
ContextMenuOpening="Grid_ContextMenuOpening">
</Grid>
</ControlTemplate>
What's a bit trickier is to get a reference to the TextBox. You need to wait until it has been loaded:
private void Grid_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
Grid grid = (Grid)sender;
ContextMenu cm = grid.ContextMenu;
if (cm != null)
cm.Opened += Cm_Opened;
}
private void Cm_Opened(object sender, RoutedEventArgs e)
{
ContextMenu cm = (ContextMenu)sender;
cm.Opened -= Cm_Opened;
MenuItem header = cm.Items[0] as MenuItem;
MenuItem child = header.Items[0] as MenuItem;
StackPanel sp = child.Header as StackPanel;
(sp.Children[1] as TextBox).Background = Brushes.Yellow;
}
Probably e.Source is not a Grid where the context menu is defined.
You can search parent elements until you will find the element where the context menu is.
private void Xxx_ContextMenuOpening(object sender, ContextMenuEventArgs e)
{
var fe = e.Source as FrameworkElement;
var contextMenu = FindContextMenu(fe);
if(contextMenu != null)
{
// your code
}
}
// helper
private ContextMenu FindContextMenu(FrameworkElement fe)
{
if(fe == null)
{
return null;
}
if(fe.ContextMenu != null)
{
return fe.ContextMenu;
}
else
{
var parent = VisualTreeHelper.GetParent(fe) as FrameworkElement;
return FindContextMenu(parent);
}
}

Setting image in runtime WPF

I have a template in app.xaml. During runtime, i want to create a button and apply this template. I also want to set the Image source during runtime.
<Application.Resources>
<ControlTemplate x:Key="btemp2" TargetType="{x:Type Button}">
<Image x:Name="myimage" HorizontalAlignment="Center" Height="84" VerticalAlignment="Center" Width="100" Margin="10,-17.5,7,-24.5" Stretch="UniformToFill"/>
</ControlTemplate>
</Application.Resources>
runtime code:
Button newButton = new Button();
newButton.Width = 100;
newButton.Height = 50;
newButton.Template = (ControlTemplate)TryFindResource("btemp2");
System.Windows.Controls.Image i = newButton.Template.FindName("myimage",this) as System.Windows.Controls.Image;
Bitmap bmp = GetIconImageFromFile(fileName);
BitmapSource src = GetBitmapImageFromBitmap(bmp);
i.Source = src;
stack.Children.Add(newButton);
It does not work as expected.
Breakpoint does not reach
Bitmap bmp = GetIconImageFromFile(fileName);
You can use Binding to set image. So you should change ControlTemplate. In that example we using Button Tag property to set image Source.
<ControlTemplate x:Key="btemp2" TargetType="{x:Type Button}">
<Image x:Name="myimage" HorizontalAlignment="Center" Height="84" VerticalAlignment="Center" Width="100" Margin="10,-17.5,7,-24.5" Stretch="UniformToFill"
Source="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Tag}"/>
</ControlTemplate>
And the Button creating code should look like this.
Button newButton = new Button();
newButton.Width = 100;
newButton.Height = 50;
newButton.Template = ( ControlTemplate )TryFindResource( "btemp2" );
tempGrid.Children.Add( newButton );
BitmapImage image = new BitmapImage(new Uri("pack://application:,,,/WPFTest;component/Images/GPlus.png"));
newButton.Tag = image;
Remove this , and use newButton in code below, and handle Loaded event :
Grd.Children.Add(newButton);
newButton.Loaded += newButton_Loaded;
...
void newButton_Loaded(object sender, RoutedEventArgs e)
{
Image img = (Image)newButton.Template.FindName("myimage", newButton);
...
}

How to create custom button from custom control?

I'm testing some codes about custom control. I had the following styles defines in Themes folder.LayerGrid.xaml.A button with an image and text. This PanelButtonStylestyle is used in layergrid.cs.
<Style TargetType="{x:Type common:LayerGrid}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<DockPanel VerticalAlignment="Stretch" HorizontalAlignment="Stretch" LastChildFill="True"
Name="PART_ParentPanel">
<DockPanel.Resources>
<Style x:Key="PanelButtonStyle" TargetType="Button">
<Setter Property="OverridesDefaultStyle" Value="True"></Setter>
<Setter Property="Focusable" Value="False"></Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="BorderPath" Margin="0"
BorderThickness="0" Background="{StaticResource TabItemBackgroundBrushUnselected}"
BorderBrush="{StaticResource TabItem_BorderBrush_Selected}">
<StackPanel Orientation="Horizontal">
<Image Source="/MCLF;component/Images/图像 3.png" Width="15" Height="15"
HorizontalAlignment="Center " VerticalAlignment="Center"></Image>
<TextBlock TextTrimming="CharacterEllipsis"
Text ="{TemplateBinding Name}"></TextBlock>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DockPanel.Resources>
<StackPanel Name="PART_BottomCntl" Orientation="Horizontal" DockPanel.Dock="Bottom" Background="AliceBlue"></StackPanel>
<StackPanel Name="PART_LeftCntl" Orientation="Horizontal" DockPanel.Dock="Left" Background="AliceBlue">
<StackPanel.LayoutTransform>
<RotateTransform Angle="90"/>
</StackPanel.LayoutTransform>
</StackPanel>
<StackPanel Name="PART_RightCntl" Orientation="Horizontal" DockPanel.Dock="Right" Background="AliceBlue">
<StackPanel.LayoutTransform>
<RotateTransform Angle="90"/>
</StackPanel.LayoutTransform>
</StackPanel>
<Grid Name="PART_MasterGrid" IsSharedSizeScope="True" Background="AliceBlue"></Grid>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
private Button AddToColumnStackPanel(Layer layer)
{
var btn = new Button
{
//Background = Brushes.Transparent,
//BorderBrush = Brushes.Transparent,
//BorderThickness = new Thickness(0),
//Height = 22,
//MinWidth = 55.0,
Padding = new Thickness(10, 0, 10, 0),
//FontWeight = FontWeights.Bold,
Style = (Style)PART_MasterGrid.FindResource("PanelButtonStyle"),
};
btn.Click += (o, e) =>...
}
And the DP Layer.Name is determined in MainWindow.xaml with
<controls:Layer Level="1" Orientation="Column" Name="Symbols" ColumnLocation="Left">
<controls:Layer.Content>
<Grid>
<Grid.DataContext>
<vm:MainViewModel/>
</Grid.DataContext>
</Grid>
</controls:Layer.Content>
Now the problem is the DP Name=Symbols is not bind correctly into the button PanelButtonStyle
I read a similar post but that example set the whole target type to the DP somecustomControlWPF Custom Control: TemplateBinding to Image
Update: The DP Name is in class Layer,which is used to define the properties of each layer's location, orientation, name, content etc... The class LayerGrid serves as the class backing the custom control.
In LayerGrid.cs we have:
public class LayerGrid : ContentControl
{
...
}
public class Layer : UIElement
{
public enum LayerOrientation
{
Row,
Column
}
public enum LayerColumnLocation
{
Left,
Right
}
public static readonly DependencyProperty LevelProperty;
public static readonly DependencyProperty ContentProperty;
public static readonly DependencyProperty OrientationProperty;
public static readonly DependencyProperty NameProperty;
public static readonly DependencyProperty ColumnLocationProperty;
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
static Layer()
{
NameProperty = DependencyProperty.Register(
"Name",
typeof(string),
typeof(Layer));
...
}
}
And LayerGrid.xaml serves the purpose of laying out the general panels; Each button will be added to the corresponding stackPanel such as PART_LeftCntl by calling the function above.
<DockPanel>
<StackPanel Name="PART_BottomCntl" Orientation="Horizontal" DockPanel.Dock="Bottom" Background="AliceBlue"></StackPanel>
<StackPanel Name="PART_LeftCntl" Orientation="Horizontal" DockPanel.Dock="Left" Background="AliceBlue">
<StackPanel.LayoutTransform>
<RotateTransform Angle="90"/>
</StackPanel.LayoutTransform>
</StackPanel>
<StackPanel Name="PART_RightCntl" Orientation="Horizontal" DockPanel.Dock="Right" Background="AliceBlue">
<StackPanel.LayoutTransform>
<RotateTransform Angle="90"/>
</StackPanel.LayoutTransform>
</StackPanel>
<Grid Name="PART_MasterGrid" IsSharedSizeScope="True" Background="AliceBlue"></Grid>
</DockPanel>
And MainWindow.xaml is responsible for giving the contents inside;
Update2 Below an example of the same style is attached.Only Buttonstyle is changed.https://www.dropbox.com/sh/os34tr8zl21uj4o/AAC_segoCWzAbJMFzCKHyZnpa?dl=0
//var btn = new Button
//{
// Background = Brushes.Transparent,
// BorderBrush = Brushes.Transparent,
// BorderThickness = new Thickness(0),
// Height = 22,
// MinWidth = 65.0,
// Padding = new Thickness(10, 0, 15, 0),
// FontWeight = FontWeights.Bold,
// Style = (Style)PART_MasterGrid.FindResource("buttonStyle"),
// Content = layer.Name
//};
var btn = new Button
{
Padding = new Thickness(10, 0, 10, 0),
//FontWeight = FontWeights.Bold,
Style = (Style)PART_MasterGrid.FindResource("PanelButtonStyle")
//Content = layer.Name
};
I think you are looking for how to create the binding in code behind.
So this should work for you
Binding b = new Binding("Name");
b.Source = layer;
btn.SetBinding(Button.ContentProperty, b);
so remove Content = layer.Name & add this code before btn.Click += (o, e) =>...
give it a try and see if this is what you are looking for
EDIT
After looking at your implementation I found that the buttons are directly added to parts (StackPanel) of the layered grid template instead of layer (see below). So Relative source may not help here.
However, there are ways how you can achieve your goal in this scenario. As an easy option you can leverage Content property of Button.
So start by binding the Text property it to the template's Content property
<TextBlock TextTrimming="CharacterEllipsis" FontSize="10"
Text ="{TemplateBinding Content}"></TextBlock>
then in the code you can simply use Content = layer.Name if the Name is not supposed to change.
eg
var btn = new Button
{
Padding = new Thickness(10, 0, 10, 0),
Style = (Style)PART_MasterGrid.FindResource("PanelButtonStyle"),
Content = layer.Name
};
Or alternatively you can bind Name with Content property to reflect the changes if needed.
eg
var btn = new Button
{
Padding = new Thickness(10, 0, 10, 0),
Style = (Style)PART_MasterGrid.FindResource("PanelButtonStyle")
};
Binding b = new Binding("Name");
b.Source = layer;
btn.SetBinding(Button.ContentProperty, b);
Let me know if this helps.

Binding chart LegendItem checkbox to series visibility in WPF in C# codebehind

I have multiple column series chart getting generated in C#. I am further trying to get the legend for this chart with the checkboxes. Such that the chart displays the column series for only legend items that are checked.
I need to do this in C# code behind and not in HTML.
I have the below existing code that creates the multiple dynamic column series -
foreach (KeyValuePair<int, string> item in list)
{
foreach (System.Data.DataRow dRow in dtTable.Rows)
{
<formation of listSource>
}
ColumnSeries ser = new ColumnSeries { Title = item.Value, IndependentValueBinding = new Binding("Key"), DependentValueBinding = new Binding("Value") };
ser.ItemsSource = null;
ser.ItemsSource = listSource;
ser.DataPointStyle = columnStyleBrown;
mcChart.Series.Add(ser);
i++;
}
}
And I further want to add something to -
ser.LegendItemStyle =
So I need to know how to create a legend style with the check boxes in c#.
There can be 2 ways of achieving this-
Either by modifying the existing legend to contain check boxes also (preferred)
Or to create a new legend altogether
Can anyone please help?
Thanks in advance!
Was able to resolve this -
xaml code -
<Grid Name="LayoutRoot">
<Grid.Resources>
<Style x:Key="CategoryLegendItem" TargetType="DVC:LegendItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DVC:LegendItem">
<StackPanel Orientation="Horizontal">
<CheckBox VerticalAlignment="Center" IsChecked="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Owner.Visibility, Mode=TwoWay, Converter={StaticResource BooleanToVisibilityConverter1}}" Margin="0,0,3,0" />
<Rectangle Width="8" Height="8" Fill="{Binding Background}" Stroke="{Binding BorderBrush}" StrokeThickness="1" Margin="0,0,3,0" />
<DV:Title VerticalAlignment="Center" Content="{TemplateBinding Content}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<DVC:Chart Name="mcChart" >
</DVC:Chart>
Relevant C# code for dynamic column series -
ColumnSeries ser = new ColumnSeries { Title = kvpNuclide.Value, IndependentValueBinding = new Binding("Key"), DependentValueBinding = new Binding("Value") };
ser.ItemsSource = null;
ser.ItemsSource = listRelease;
ser.DataPointStyle = columnStyleAqua;
ser.LegendItemStyle = (Style)LayoutRoot.Resources["CategoryLegendItem"];
mcChart.Series.Add(ser);

When text wraps within TextBlock, ActualHeight is incorrect

I have a TextBlock with a Border around it that's inside of a Canvas that I'm using to animate it as part of a custom control. The block slides in from the bottom of the screen over the top of the image. I'm trying to use the ActualHeight of the TextBlock to determine how far to move it onto the page, but when there is so much text that it wraps to two lines, the ActualHeight returns the same size as though there was a single line.
TextBlock:
<DataTemplate DataType="{x:Type contentTypes:BusinessAdText}" x:Key="BusinessAdTextTemplate">
<Border Background="#a9a9a975"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}}, Path=ActualWidth}">
<TextBlock Margin="20" Text="{Binding Text}"
TextWrapping="Wrap">
</TextBlock>
</Border>
</DataTemplate>
This style is applied which has the canvas:
<Style TargetType="local:BusinessAd">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:BusinessAd">
<Border Background="Transparent">
<Canvas ClipToBounds="True">
<ContentPresenter x:Name="PART_Content"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
</Canvas>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Code behind for BusinessAd.cs has:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_contentPart = GetTemplateChild("PART_Content") as FrameworkElement;
}
Then just using a simple DoubleAnimation I move it onto the screen:
if (_contentPart != null && _isLoaded)
{
_storyboard.Stop();
vAnimation.From = ActualHeight;
vAnimation.To = ActualHeight - _contentPart.ActualHeight;
//_contentPart.ActualHeight returns 46.something no matter how much text is there
vAnimation.Duration = new Duration(TimeSpan.FromSeconds(Duration));
if (_storyboard.Children.Count == 0)
{
_storyboard.Children.Add(vAnimation);
Storyboard.SetTargetProperty(vAnimation, new PropertyPath("(Canvas.Top)"));
Storyboard.SetTarget(vAnimation, _contentPart);
}
_storyboard.Begin();
}
You have to call UpdateLayout() before checking ActualHeight:
if (_contentPart != null && _isLoaded)
{
_storyboard.Stop();
UpdateLayout();
vAnimation.From = ActualHeight;
vAnimation.To = ActualHeight - _contentPart.ActualHeight;
//_contentPart.ActualHeight returns 46.something no matter how much text is there
vAnimation.Duration = new Duration(TimeSpan.FromSeconds(Duration));
if (_storyboard.Children.Count == 0)
{
_storyboard.Children.Add(vAnimation);
Storyboard.SetTargetProperty(vAnimation, new PropertyPath("(Canvas.Top)"));
Storyboard.SetTarget(vAnimation, _contentPart);
}
_storyboard.Begin();
}
I'm not sure if this applies to you, but for me, the textblock in Windows.UI.Xaml.Controls needs to be preceded with this:
myTextBlock.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
Before, when I had just myTextBlock.Measure(new Size());, it worked when there wasn't any text wrapping, but with wrapping ActualWidth and ActualHeight returned the dimensions of the word/letter, depending on WrapWholeWords or Wrap

Resources