What I'm attempting to do:
Create a custom page control that consumers can use just like the UWP page, but, that also displays it's own custom content along side the consumers content.
What have I tried:
Creating a new Control, inheriting from Page
Creating a templated control that inherits from page
Creating a control that contains a page
Setting the ContentProperty attribute and binding to it in my custom page
What is the problem?
When I attempt to create a control that has both a xaml and xaml.cs file, that inherits from Page I get InvalidCastExceptions on random controls inside the subclassed control.
Example:
TestPage.xaml
<Page
x:Class="ControlSandbox.TestPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:uwp_toolkit="using:Microsoft.Toolkit.Uwp.UI.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Page.Content>
<ContentPresenter Content="{Binding}" />
</Page.Content>
<Page.BottomAppBar>
<AppBar Background="Transparent" x:Name="appbar" IsOpen="True">
<uwp_toolkit:InAppNotification x:Name="note">
<StackPanel>
<TextBlock>HEADER!</TextBlock>
<TextBlock>Message</TextBlock>
<StackPanel Orientation="Horizontal">
<Button>OK</Button>
<Button>Cancel?</Button>
</StackPanel>
</StackPanel>
</uwp_toolkit:InAppNotification>
</AppBar>
</Page.BottomAppBar>
</Page>
TestPage.xaml.cs
public partial class TestPage : Page
{
public TestPage()
{
this.InitializeComponent();
}
public void ShowNotification()
{
appbar.IsOpen = true;
note.Show();
}
}
MainPage.xaml
<controlsandbox:TestPage
xmlns:controlsandbox="using:ControlSandbox" x:Class="ControlSandbox.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Button Click="Button_Click">SHOW NOTIFICATION</Button>
</Grid>
</controlsandbox:TestPage>
MainPage.xaml.cs
public partial class MainPage : TestPage
{
public MainPage()
{
this.InitializeComponent();
}
private void Button_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
this.ShowNotification();
}
}
The above code results in an InvalidCastException and for the life of me I can't find the problem.
System.InvalidCastException: 'Unable to cast object of type
'Windows.UI.Xaml.Controls.AppBar' to type
'Windows.UI.Xaml.Controls.Button'.'
Now if I do the same exact code, but all in the MainPage.xaml instead of in the TestPage.xaml everything works as expected
Update
So I believe this is a bug in the platform. Here is a demo I did of the issue. Please prove me wrong because this would be a real limitation https://github.com/DotNetRussell/UWP_Page_Inheritance_Bug
Update
I added the changes for the answer below. It seems that when I create a normal templated control and put it on a vanilla uwp page, it works fine. However, when I create a templated Page, it ignores my template.
Update
I think this is a bug in the platform. I opened an issue up on github https://github.com/microsoft/microsoft-ui-xaml/issues/1075
The problem is that if create custom base page with Xaml, it will be mandatory converted to the subpage's content, if the sub-page contained controls different with Base page will throw exception. And the better way is create base class without xaml and add the base page content in the code behind. For more code please refer the following .
public class BasePage : Page
{
protected override void OnNavigatedTo(NavigationEventArgs e)
{
this.BottomAppBar = new AppBar()
{
Background = new SolidColorBrush(Colors.Transparent),
IsOpen = false,
Content = new InAppNotification
{
Content = new StackPanel
{
Children =
{
new TextBlock{ Text = "HEADER!"},
new TextBlock{Text = "Message"},
new StackPanel
{
Orientation = Orientation.Horizontal,
Children=
{
new Button{Content = "ok"},
new Button {Content = "cancel"}
}
}
}
}
}
};
base.OnNavigatedTo(e);
}
public void ShowNotification()
{
this.BottomAppBar.IsOpen = true;
InAppNotification note = this.BottomAppBar.Content as InAppNotification;
if (note != null)
note.Show();
}
}
Usage
public sealed partial class MainPage : BasePage
{
public MainPage()
{
this.InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
ShowNotification();
}
}
Xaml
<local:BasePage
x:Class="CustomPage.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:CustomPage"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
>
<Grid>
<Button Click="Button_Click" Content="ClikMe" />
</Grid>
</local:BasePage>
Create a Templated Control (Project->Add New Item->Templated Control in Visual Studio):
public sealed class CustomPage : Page
{
private AppBar appbar;
private InAppNotification note;
public CustomPage()
{
this.DefaultStyleKey = typeof(CustomPage);
}
protected override void OnApplyTemplate()
{
appbar = GetTemplateChild("appbar") as AppBar;
note = GetTemplateChild("note") as InAppNotification;
}
public void ShowNotification()
{
if (appbar != null)
appbar.IsOpen = true;
note?.Show();
}
}
...and define a custom Style in Themes/Generic.xaml:
<Style TargetType="local:CustomPage">
<Setter Property="BottomAppBar">
<Setter.Value>
<AppBar Background="Transparent" x:Name="appbar" IsOpen="True">
<uwp_toolkit:InAppNotification x:Name="note">
<StackPanel>
<TextBlock>HEADER!</TextBlock>
<TextBlock>Message</TextBlock>
<StackPanel Orientation="Horizontal">
<Button>OK</Button>
<Button>Cancel?</Button>
</StackPanel>
</StackPanel>
</uwp_toolkit:InAppNotification>
</AppBar>
</Setter.Value>
</Setter>
</Style>
There is .xaml.cs file for the CustomPage base class.
Edit: Since the BottomAppBar is not part of the template, you need to wait to access the elements in it until they have actually been created. Just do this in the method:
public sealed class CustomPage : Page
{
public CustomPage()
{
this.DefaultStyleKey = typeof(CustomPage);
}
public void ShowNotification()
{
AppBar appBar = this.BottomAppBar;
appBar.IsOpen = true;
InAppNotification note = appBar.Content as InAppNotification;
if(note != null)
note.Show();
}
}
Related
Hi everyone i am a newbie in UWP development, i have searched a lot on the net but I have not found the right way to achieve my goal, what i would like to do is update the ui of my MainPage through the use of a class belonging to UiUpdate class. Here's what I'd like to get, this is my MainPage:
namespace Test_App
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
}
}
This is the relative xaml associated with my MainPage:
<Page
x:Class="Test_App.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Test_App"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<controls:DockPanel>
<controls:DockPanel Visibility="Visible" HorizontalAlignment="Left" >
<Grid>
<TextBlock x:Name="text_one" HorizontalAlignment="Right" Margin="0,50,46,0" VerticalAlignment="Center" FontWeight="Bold" />
<TextBlock x:Name="text_two" HorizontalAlignment="Left" VerticalAlignment="Bottom" FontWeight="Medium" />
</Grid>
</controls:DockPanel>
</Grid>
</Page>
Now through the UiUpdate class, I would like to update my TextBlocks or any other element of my UI, I found something like this on the net:
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync (CoreDispatcherPriority.Normal, () =>
{
// Update texboxt
});
But i can't access in any way in the UiUpdate class the texbox elements of the MainPage i can't do MainPage.text_one .., also i can't find good documentation for what concerns patterns to be applied or other for the realization of my UWP app
How to update item xaml from another class in UWP
For explain this question, you may need to refer UWP mvvm design, and you mentioned UiUpdate most like ViewModel.
For example
<Page.DataContext>
<local:UiUpdate x:Name="ViewModel" />
</Page.DataContext>
<Grid>
<TextBlock
x:Name="text_one"
Margin="0,50,46,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding TextBlockText}" />
<Button VerticalAlignment="Bottom" Click="Button_Click">Update</Button>
</Grid>
Code behind
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private Random random = new Random();
private void Button_Click(object sender, RoutedEventArgs e)
{
ViewModel.TextBlockText = $"Text-----{random.Next(15)}";
}
}
public class UiUpdate : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private string _textBlcokText;
public string TextBlockText
{
get
{
return _textBlcokText;
}
set
{
_textBlcokText = value;
OnPropertyChanged();
}
}
}
I have created custome window (titlebar, min/max/ext buttons, own border for window manipulation and lots of styles and triggers).
There are 5 methods defined (which i would like to override):
From window markup:
SourceInitialized="Window_SourceInitialized"
Closing="Window_Closing"
From Titlebar buttons:
Exit_Click()
Max_Click()
Min_Click()
And at last I have DockPanel
<DockPanel Name="ClientArea"/>
In which I want to put my content
I have tried to add content from code:
BaseWindow editInterfaceWindow = new BaseWindow() { Owner = this };
editInterfaceWindow.DataContext = new EditInterface();
editInterfaceWindow.ShowDialog();
But this way some bindings stoped working and inside editInterfaceWindow I cant create another window this way because of Owner = this. There are also some problems with InitializeComponent() in constructor.
And ListView inside EditInterface UserControl <ListView Name="LBAvaliable" ItemsSource="{Binding AvaliableFaces, UpdateSourceTrigger=PropertyChanged}"> is not visible in code as LBAvaliable.
I have used that window few times, filling ClientArea with content by hand.
How should I create other windows, so that I can just inherit it or just define binding? So my XAML for every single window does not take ~1000 lines of code.
In the past I've used MVVMCross Framework and we never had to worry about this ourselves. Though this is not the best, here's an idea on what you can do.
Create a view model that can be overridden for your user control.
Set data templates.
Programmatically change the view model for your user control's main content and let data templates do the work for the UI.
View Model: Pre-defined 3 button actions ready for you to set/override.
public class MainUCViewModel : ViewModelBase
{
private Action<object> btnACommand;
private Action<object> btnBCommand;
private Action<object> btnCCommand;
private object ccVM;
public ViewModelBase CCVM
{
get { return this.ccVM; }
set
{
this.ccVM = value;
OnPropertyChanged(); // Notify View
}
}
public MainUCViewModel()
{
}
public RelayCommand BtnACommand
{
get { return new RelayCommand(btnACommand); }
}
public RelayCommand BtnBCommand
{
get { return new RelayCommand(btnBCommand); }
}
public RelayCommand BtnCCommand
{
get { return new RelayCommand(btnCCommand); }
}
public void SetBtnACommand(Action<object> action)
{
this.btnACommand = action;
}
public void SetBtnBCommand(Action<object> action)
{
this.btnBCommand = action;
}
public void SetBtnCCommand(Action<object> action)
{
this.btnCCommand = action;
}
}
View:
<UserControl x:Class="WpfApplication1.Views.UserControls.MainUC"
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"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="750">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="45" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding BtnACommand}" Width="100">
<TextBlock>A</TextBlock>
</Button>
<Rectangle Width="15" />
<Button Command="{Binding BtnBCommand}" Width="100">
<TextBlock>B</TextBlock>
</Button>
<Rectangle Width="15" />
<Button Command="{Binding BtnCCommand}" Width="100">
<TextBlock>C</TextBlock>
</Button>
</StackPanel>
</Grid>
<Grid Grid.Row="1">
<ContentControl x:Name="CCMain" Content="{Binding CCVM}"/>
</Grid>
</Grid>
</UserControl>
Look at Thinking with MVVM: Data Templates + ContentControl. Simply define the data template for your view model.
<Window.Resources>
<DataTemplate DataType="{x:Type ViewModel:GeneralSettingsViewModel}">
<View:GeneralSettingsView/>
</DataTemplate
<DataTemplate DataType="{x:Type ViewModel:AdvancedSettingsViewModel}">
<View:AdvancedSettingsView/>
</DataTemplate>
</Window.Resources>
What I’m saying here is that GeneralSettingsViewModel should be
rendered using a GeneralSettingsView. That’s exactly what we need !
Because the Views are created using a DataTemplate, we do not need to
setup the DataContext, it will be automatically registered to the
templated object, the ViewModel.
There are two main approaches to your problem:
Inherited windows
Configurable windows
For approach 1, design your window and make the methods overrideable:
In base window xaml, assign the handlers and everything you want:
<Window x:Class="WpfTests.MainWindow"
...
SourceInitialized="Window_SourceInitialized">
In base window, define the handlers as protected virtual (or abstract, if you like to enforce their implementation)
public partial class MainWindow : Window
{
// ...
protected virtual void Window_SourceInitialized(object sender, EventArgs e)
{
}
// ...
}
Create derived windows
public class ExWindow : MainWindow
{
protected override void Window_SourceInitialized(object sender, EventArgs e)
{
// specialized code here
}
}
Change App.xaml to use Startup instead of StartupUri
<Application x:Class="WpfTests.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Startup="Application_Startup">
And manually create your first window, chosing one of the inherited window classes
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
var window = new ExWindow();
window.Show();
}
}
The second approach - configurable windows - follows the same principle as a good user control design: The window/control properties are controlled by the creator instead of being controlled by the window/control itself.
So, instead of defining some event handler within the window code, just leave this exercise to the user, who hopefully knows what the window should do:
public partial class MainWindow : Window
{
// I don't care for SourceInitialized (also remove it from XAML)
}
In App.xaml or wherever a window is created:
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
var window = new MainWindow();
window.SourceInitialized += window_SourceInitialized;
window.Show();
}
void window_SourceInitialized(object sender, EventArgs e)
{
var window = sender as MainWindow;
// I know how to handle this event for this window instance
}
}
I'm simply trying to create a binding for a UserControl within a UserControl using WPF/Caliburn, but I am having trouble properly binding the nested UserControl.
The basic layout is a ShellViewModel : Conductor, and within the ShellView there is a ContentControl that is populated by the ShellViewModel.ActivateItem method that loads a UserControl (PageViewModel), and within the PageView UserControl there is a nested UserControl called "SimpleControl".
The binding works when the page loads (it displays the "Initial Text Value" string in the nested UserControl), but it does not appear to be bound to the PropertyChanged event on PageView (and never updates its value when the test button is pressed). The Label in the Parent UserControl (PageView) is bound correctly/updates as expected.
PageView.xaml:
<UserControl x:Class="CaliburnTest.Views.PageView"
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:CaliburnTest.Views"
xmlns:cal="http://www.caliburnproject.org"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel Background="Aqua">
<Label Content="{Binding TextLabelTest, FallbackValue=DEFAULT}"></Label>
<local:SimpleControl cal:Bind.Model="WPFCaliburnTemplate.Views.PageView" TextValue="{Binding TextLabelTest}"></local:SimpleControl>
<Button Name="UpdateTextButton">Update Text</Button>
</StackPanel>
</UserControl>
PageViewModel.cs:
using Caliburn.Micro;
namespace CaliburnTest.ViewModels
{
public class PageViewModel : Screen
{
private string _textLabelTest;
public string TextLabelTest
{
get { return _textLabelTest; }
set
{
_textLabelTest = value;
NotifyOfPropertyChange(() => TextLabelTest);
}
}
public PageViewModel()
{
TextLabelTest = "Initial Text Value";
}
public void UpdateTextButton()
{
TextLabelTest = "Updated Text Value";
}
}
}
SimpleControl.xaml:
<UserControl x:Class="CaliburnTest.Views.SimpleControl"
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:cal="clr-namespace:Caliburn.Micro;assembly=Caliburn.Micro.Platform.Core"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Border Margin="10" BorderThickness="1" BorderBrush="#FF9A9A9A">
<StackPanel>
<Label Name="TextLabel"></Label>
</StackPanel>
</Border>
</UserControl>
And finally SimpleControl.xaml.cs:
using System.Windows;
using System.Windows.Controls;
namespace CaliburnTest.Views
{
public partial class SimpleControl : UserControl
{
public SimpleControl()
{
InitializeComponent();
}
public static DependencyProperty TextValueProperty = DependencyProperty.Register("TextValue", typeof(string), typeof(SimpleControl),
new FrameworkPropertyMetadata("", TextValueChangedCallBack));
public string TextValue
{
get { return (string)GetValue(TextValueProperty); }
set
{
SetValue(TextValueProperty, value);
Refresh();
}
}
protected static void TextValueChangedCallBack(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
(sender as SimpleControl).TextValue = (string)e.NewValue;
}
private void Refresh()
{
TextLabel.Content = TextValue;
}
}
}
I have been banging on this all day and cannot figure out what I'm doing wrong. I've tried all combinations of different DataContexts, RelativeSources, and whatever else I can find on SO and Google but am still coming up short. I have a much more complicated custom UserControl I'm trying to work with but I created this simple example to try and figure out the issue.
I played around with all sorts of combinations and I believe I found a result that appears to work, though I'm not entirely sure why:
First, I changed the way I was binding the "TextValue" dependency property to:
<local:SimpleControl TextValue="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type UserControl}}, Path=DataContext.TextLabelTest}" />
However, this was still only working for the initial value and the binding was not updating when the string was updated. In the end, I had to edit SimpleControl.xaml.cs like so:
protected static void TextValueChangedCallBack(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
// (sender as SimpleControl).TextValue = (string)e.NewValue;
(sender as SimpleControl).Refresh();
}
As you can see, I commented out the line setting the SimpleControl.TextValue (and in turn setting the TextValueProperty DependencyProperty) and simply called the SimpleControl.Refresh() method to update the label. I'm not sure why setting the TextValueProperty was breaking the DependencyProperty (even though it was already set when the string updated), but I think I need to go brush up on some MSDN articles!
I have a checkbox in my datatemplate and for some reason the events are not firing. see code below. my datatemplate is in a resource dictionary with code behind file. Any Ideas?
<ResourceDictionary x:Class="ArmyBuilder.Templates.EquipmentDataTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<DataTemplate x:Key="EquipmentDataTemplate">
<Grid>
<CheckBox Content="{Binding Name}" Checked="ToggleButton_OnChecked" Click="CheckBox_Click"/>
</Grid>
</DataTemplate>
//code behind
namespace ArmyBuilder.Templates
{
public partial class EquipmentDataTemplate : ResourceDictionary
{
public EquipmentDataTemplate()
{
InitializeComponent();
}
private void ToggleButton_OnChecked(object sender, RoutedEventArgs e)
{
// breakpoint not hit
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
// breakpoint not hit
}
}
}
I am not sure how you use it, but the your code works for me and the click event got fired. Check the following and if you still cannot find the point, share a repro project to show how you used it.
Template XAML:
<ResourceDictionary x:Class="App10.EquipmentDataTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<DataTemplate x:Key="EquipmentDataTemplate">
<Grid>
<CheckBox Content="Click Me" Checked="ToggleButton_OnChecked" Click="CheckBox_Click"/>
</Grid>
</DataTemplate>
</ResourceDictionary>
Template cs:
namespace App10
{
public sealed partial class EquipmentDataTemplate : ResourceDictionary
{
public EquipmentDataTemplate()
{
this.InitializeComponent();
}
private void ToggleButton_OnChecked(object sender, RoutedEventArgs e)
{
// breakpoint not hit
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
// breakpoint not hit
}
}
}
In MainPage.Xaml, use the template in a ListView:
<Page
x:Class="App10.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App10"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<local:EquipmentDataTemplate></local:EquipmentDataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView x:Name="listView" CanReorderItems="True" AllowDrop="True" ItemTemplate="{StaticResource EquipmentDataTemplate}">
</ListView>
</Grid>
</Page>
In MainPage cs:
namespace App10
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
var list = new ObservableCollection<string>();
for (int i = 0; i < 10; i++)
{
list.Add("Item " + i);
}
listView.ItemsSource = list;
}
}
}
I have a simple UserControl that I want to display in my stackpanel programmatically
.
When I do, the UC does not show on the screen. If I drag a single instance from the toolbox it works fine.
The User Control is XAML is
<UserControl x:Class="MYProj.Controls.SpecialNumberOption"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="200">
<Viewbox>
<Grid x:Name="LayoutRoot" Background="White" Width="200" Height="300">
<Button x:Name="buttonMe" Content="Button" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="180" Height="150" Style="{StaticResource NumberButtonStyle}" Click="buttonMe_Click"/>
<TextBlock x:Name="subText" HorizontalAlignment="Left" Margin="10,165,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Height="125" Width="180" FontStyle="Italic" TextAlignment="Center"/>
</Grid>
</Viewbox>
</UserControl>
The Codebehind
public partial class SpecialNumberOption : UserControl
{
public event RoutedEventHandler Click;
public SpecialNumberOption()
{
InitializeComponent();
this.applyStyle();
}
public SpecialNumberOption(SurveyQuestionOption option)
{
this.buttonMe.Content = option.Text;
this.subText.Text = option.SubText;
this.applyStyle();
}
private void applyStyle()
{
this.buttonMe.FontSize = 26;
this.buttonMe.Background = standardBackground;
this.buttonMe.Foreground = standardForecolor;
}
///Raise the event to the outside
private void buttonMe_Click(object sender, RoutedEventArgs e)
{
Click(this, e);
}
}
Implementation
This is how Im adding the control
foreach (var y in x.Options)
{
//Create new instance from An object
var r = new SpecialNumberOption(y);
// Set visibility
r.Visibility = System.Windows.Visibility.Visible;
r.IsEnabled = false;
//Assign the event handler
r.Click += r_Click;
//This is my stackpanel
listOptions.Children.Add(r);
....
}
//Handle the click event
void r_Click(object sender, RoutedEventArgs e)
{
SpecialNumberOption o = (SpecialNumberOption)e.OriginalSource;
....
}
Update
I found when I use the alternate Constructor this is when it ceases to work.
I have to use the default constructor. Is this normal?
I have not checked all of your posted code for correctness but here is the issue with your constructors: you must call InitializeComponent (and it must happen before you access any named elements)
here is a version with a fix:
public SpecialNumberOption()
{
InitializeComponent();
this.applyStyle();
}
public SpecialNumberOption(SurveyQuestionOption option) : this () //will call the empty default constructor
{
this.buttonMe.Content = option.Text;
this.subText.Text = option.SubText;
}
Remark: I consider it bad style for controls to have more than the empty default constructor.
The parameters should be set via a property setter. It enable you and whoever will be reusing your control to use and parameterize it in xaml.