I have a usercontrol:
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Name="TextBlock1" Text="Text for"></TextBlock>
</Grid>
It has a cs file:
public partial class MyUserControl: UserControl
{
public string MyProperty { get; set; }
public MyUserControl()
{
InitializeComponent();
DoSomething();
}
private void DoSomething()
{
TextBlock1.Text = TextBlock1.Text + MyProperty;
}
}
I'm trying to add this usercontrol programatically to another user control:
UserControls.MyUserControl myUserControl = new UserControls.MyUserControl();
myUserControl.MyProperty = "Something";
MyStackPanel.Children.Add(myUserControl);
I thought I've done something like this before without any issues, but in this case MyProperty is always null. What did I do wrong?
Your code is fine as-is except for when you are calling DoSomething(). You should avoid GUI interaction in the constructor of a control for many reasons (not the least of which is that your property is not yet set as pointed out by Mark Hall). I do not however recommend adding different constructors just to take initial properties.
You simply want to defer that call to the Loaded event of the control.
Assuming you set the event in the Xaml like this:
<navigation:Page x:Class="MyUserControl"
...
Loaded="Page_Loaded">
Your code would look like:
public partial class MyUserControl: UserControl
{
public string MyProperty { get; set; }
public MyUserControl()
{
InitializeComponent();
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
DoSomething();
}
private void DoSomething()
{
TextBlock1.Text = TextBlock1.Text + MyProperty;
}
}
If you want to set the Loaded event in code it would just look like:
public partial class MyUserControl: UserControl
{
public string MyProperty { get; set; }
public MyUserControl()
{
InitializeComponent();
Loaded += Page_Loaded;
}
private void Page_Loaded(object sender, RoutedEventArgs e)
{
DoSomething();
}
private void DoSomething()
{
TextBlock1.Text = TextBlock1.Text + MyProperty;
}
}
The it is just down to you setting all your desired properties after construction, but before the page is loaded as you are doing already.
UserControls.MyUserControl myUserControl = new UserControls.MyUserControl();
myUserControl.MyProperty = "Something";
MyStackPanel.Children.Add(myUserControl); // This makes it visible when Loaded event is called
What is happening is that you are calling DoSomething in your contructor before you have set MyProperty so MyProperty is equal to null. If you want the data to be there when the UserControl is created you can set it in the Constructor.
i.e.
public partial class MyUserControl : UserControl
{
public string MyProperty { get; set; }
public MyUserControl() //Default Constructor
{
InitializeComponent();
}
public MyUserControl(string MyData)
{
InitializeComponent();
MyProperty = MyData;
DoSomething();
}
private void DoSomething()
{
TextBlock1.Text = TextBlock1.Text + MyProperty;
}
}
and create it like this.
MyUserControl myUserControl = new MyUserControl(" Something");
MyStackPanel.Children.Add(myUserControl);
Related
I have an application that look like this
The whole window is defined in the MainWindow.xaml, the green part is the content control
<ContentControl Grid.Row="1"
Grid.Column="1"
Margin="5"
Content="{Binding CurrentView}"/>
The MainViewModel looks like this:
public RelayCommand HomeViewCommand { get; set; }
public RelayCommand DetailsViewCommand { get; set; }
public HomeViewModel HomeVm { get; set; }
public DetailsViewModel DetailsVm { get; set; }
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
HomeVm = new HomeViewModel();
DetailsVm = new DetailsViewModel();
CurrentView = HomeVm;
HomeViewCommand = new RelayCommand(o =>
{
CurrentView = HomeVm;
});
}
Current and default content of the MainView is the HomeView, I already implemented the event trigger on pressing the item in the list in the HomeView. I want to know, what should I write in the HomeView method (which is triggering on the click on the item) in order to change the MainView content part to another View (DetailsView in my case). Code in my HomeViewModel:
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
//something here to change the currentView of the MainViewModel
}
}
Got an answer from the guy from discord server.https://discord.gg/AvnpSMDY
You could pass the MainViewModel into the constructor of another view model and assign it to a private readonly field like _mainViewModel. This way you can change the current view either by changing the CurrentView property:
_mainViewModel.CurrentView = _mainViewModel.DetailsVm;
or executing commands:
_mainViewModel.DetailsViewCommand.Execute(null);
for my app, I've created a popup- <Window> that is bound to a ViewModel.
The view model of the popup:
public class GeneratorSelectionViewModel : BaseViewModel
{
private Window mWindow;
public ICommand SelectedCommand { get; set; }
public GeneratorListItem SelectedItem { get; set; }
public GeneratorSelectionViewModel(Window window)
{
mWindow = window;
SelectedCommand = new RelayCommand(GeneratorSelected);
}
private void GeneratorSelected(object parameter)
{
if (SelectedItem != null)
{
mWindow.DialogResult = true;
}
}
}
...that is bound to this popupwindow (here the codebehind):
public partial class GeneratorSelectionPopup : Window
{
public GeneratorSelectionPopup()
{
InitializeComponent();
this.DataContext = new GeneratorSelectionViewModel(this);
}
}
In that window, I have a listbox, where SelectedItem is bound to SelectedItem-property of the VM.
I call this Popup the following way:
GeneratorSelectionPopup SelectionPopup = new GeneratorSelectionPopup();
SelectionPopup.ShowDialog();
Now my question...
How can I transfer that public property SelectedItem from the popups viewmodel to the MainWindow?
`SelectionPopup.SelectedItems` isn't available.
Cast the DataContext of the window:
GeneratorSelectionPopup SelectionPopup = new GeneratorSelectionPopup();
SelectionPopup.ShowDialog();
var selectedItem = (SelectionPopup.DataContext as GeneratorSelectionViewModel).SelectedItem;
You could use an event handler
public partial class GeneratorSelectionPopup : Window
{
public event EventHandler<MyEventArgs> ButtonOkClicked;
public GeneratorSelectionViewModel ViewModel;
public GeneratorSelectionPopup()
{
InitializeComponent();
ViewModel = new GeneratorSelectionViewModel(this);
this.DataContext = ViewModel
}
private void HandleOkButtonClick(object sender, RoutedEventArgs e)
{
ButtonOkClicked?.Invoke(this, new MyEventArgs
{
SelectedItem = this.ViewModel.SelectedItem
});
Window.GetWindow(this).Close();
}
}
public class MyEventArgs: EventArgs
{
public GeneratorListItem SelectedItem { get; set; }
}
And in the mainwindow cs:
GeneratorSelectionPopup SelectionPopup = new GeneratorSelectionPopup();
SelectionPopup.ButtonOkClicked += OnButtonOkClicked;
SelectionPopup.ShowDialog();
private void OnButtonOkClicked(object sender, MyEventArgs e)
{
}
I am trying to binding a number to a Enum located in the view model. I haven't been able to pass the value from the view to the viewmodel through the converter. Is this even possible? I haven't seen anything online that solves this and none of my attemps have worked either.
Viewmodel
public enum TimerOptions
{
FifteenMinutes,
OneHour,
Tomorrow
}
private ICommand _timerCommand;
public ICommand TimerCommand => _timerCommand ??
(_timerCommand = new RelayCommand<TimerOptions>(StartTimer));
private async void StartTimer(TimerOptions option){ .... }
View
<Button Command="{Binding TimerCommand}"
Tag="0"
CommandParameter="{Binding Path=Tag, Converter={StaticResource BidirectionalEnumConverter}}">15 minutes</Button>
In the above example, when the user clicks the button, I want my function to get the first enum value of FifteenMinutes as the parameter. I've tried this by adding a Tag (as seen), adding an x:Name to the Button and also playing around with Source and Path in the binding. Nothing has worked.
Is this possible? I've been trying to solve this for hours, I haven't found anything online.
I want my function to get the first enum value of FifteenMinutes as the parameter.
Why not just do the converter operation in the VM from the info passed in on a string in the command parameter?
Example
<Button Command="{Binding TimerCommand}"
Tag="1"
CommandParameter=1
Then have a ICommand based on that such as
public ICommand TimerCommand =>
new OperationCommand(async (oTag) => await StartTime(oTag).ConfigureAwait(true));
private bool IsOperation { get; set; }
private async void StartTimer(object oTag)
{
int tagId = (int)oTag;
//.... do the convert operations here...
...
// Access VM properties here
IsOperation = true;
await ....;
}
Here is the commanding structure I use instead of Relay.
public class OperationCommand : ICommand
{
#region Variables
private Func<object, bool> CanExecuteHandler { get; set; }
private Action<object> ExecuteActionHandler { get; set; }
public bool InSeparateThread { get; set; }
#endregion
#region Properties
#endregion
#region Construction/Initialization
public OperationCommand(Action<object> executeAction, bool inSeparateThread = false)
{
InSeparateThread = inSeparateThread;
ExecuteActionHandler = executeAction
?? throw new ArgumentNullException(nameof(executeAction));
}
public OperationCommand(Action<object> executeAction,
Func<object, bool> canExecute) : this(executeAction)
{
CanExecuteHandler = canExecute;
}
// Here to adhere to ICommand, change to below if needed
//public event EventHandler CanExecuteChanged;
event EventHandler ICommand.CanExecuteChanged
{
add {}
remove {}
}
#endregion
#region Methods
public bool CanExecute(object parameter) => CanExecuteHandler?.Invoke(parameter) ?? true;
// public void RaiseCanExecuteChanged() => CanExecuteChanged?.Invoke(this, new EventArgs());
public void Execute(object parameter)
{
ExecuteActionHandler?.Invoke(parameter);
}
#endregion
}
}
I'm using my usercontrol in window. I want to do something in usercontrol when window's StateChanged event fires.
I want to send statechanged event to usercontrol.
How can i do this ?
Option 1:
Define a public method in the UserControl code behind that can be called to notify the event occurance
Handle the StateChanged event in the Window code behind and call the defined method in the UserControl
Option 2:
Implement an interface for the StateChanged event and implement this interface in your Window
Implement a DependencyProperty with interface as type in the UserControl
Bind the property to your Window when you instantiate the UserControl
Register to the StateChanged event on property changed in the code behind of UserControl
Some code for demonstration how to implement and use option 2:
public interface IStateChanged
{
event EventHandler StateChanged;
}
public partial class MainWindow : Window, IStateChanged
{
public MainWindow()
{
InitializeComponent();
}
}
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public IStateChanged StateChangedHost
{
get { return (IStateChanged)GetValue(StateChangedHostProperty); }
set { SetValue(StateChangedHostProperty, value); }
}
// Using a DependencyProperty as the backing store for StateChangedHost. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StateChangedHostProperty =
DependencyProperty.Register("StateChangedHost", typeof(IStateChanged), typeof(MyUserControl), new FrameworkPropertyMetadata(null, StateChangedHost_PropertyChanged));
private static void StateChangedHost_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var self = d as MyUserControl;
if (e.OldValue != null)
{
((IStateChanged)e.OldValue).StateChanged -= self.NotifyStateChanged;
}
if (e.NewValue != null)
{
((IStateChanged)e.NewValue).StateChanged += self.NotifyStateChanged;
}
}
private void NotifyStateChanged(object sender, EventArgs e)
{
// implementation logic on StateChanged event
}
}
<Window [...]>
<Grid>
<local:MyUserControl StateChangedHost="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"/>
</Grid>
</Window>
Add a method in the User Control to notify it
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void MainWindow_StateChanged(object sender, EventArgs e)
{
myUserControl.StateChanged();
}
}
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public StateChanged()
{
...
}
}
<Window [...]>
<Grid>
<local:MyUserControl x:Name="myUserControl"/>
</Grid>
</Window>
This should work
public partial class MyUserControl : UserControl
{
public MyUserControl ()
{
InitializeComponent();
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(Window.WindowStateProperty, typeof(Window));
dpd.AddValueChanged(Application.Current.MainWindow, (s, e) =>
{
//your code
});
}
}
Basically it tells the user control to observe WindowsStateProperty and any time that state changes it will run
I think, that's not the right approach.
my code would look like this:
public class MyUserControl : UserControl{
event EventHandler ParentWindowStateChanged;
public void RaiseParentWindowStateChanged(Window sender){
this.ParentWindowStateChanged?.Invoke(sender, EventArgs.Empty);
}
}
on Window.StateChanged you can call myUserControl. RaiseParentWindowStateChanged(this).
in constructor of UserControl you can add handler for event ParentWindowStateChanged like
MyUserControl(){
this. ParentWindowStateChanged += (sender, args) => {
// do something
};
Regards
Steffen
I would like that my UIElement control acts like a LinkButton in ASP.Net when clicked, and sends an CommandArgument on MouseLeftButtonUp. I suppose I need to make a custom event for this, so I created a OnCommand event like this:
public delegate void OnCommand(object sender, CommandEventArgs e);
public class CommandEventArgs : EventArgs
{
public string CommandArgument { get; set; }
}
How can I add this event to MouseLeftButtonUp on my UIElement, and also pass the CommandArgument? Or is there another way to accomplish the effect of a LinkButton?
I didnt figure out a solution with custom events, so I made a UserControl instead, with a CommandArgument and CommandName properties.
public partial class LinkButton : UserControl
{
public string CommandArgument { get; set; }
public string CommandName { get; set; }
public LinkButton()
{
InitializeComponent();
CommandArgument = String.Empty;
CommandName = String.Empty;
}
public void AddElemnent(UIElement obj)
{
LayoutRoot.Children.Add(obj);
}
}