Bubbling up a button command in UserControl - c#

I have a button in a user control called LauncherView + LauncherViewModel, I add this to the main window called MainView + MainViewModel.
When I click on the button I would like to capture the event in the MainViewModel. How can I do this?
In the LauncherViewModel is easy enough with:
RelayCommand launchCommand;
public ICommand LaunchCommand{
get{
if (launchCommand == null){
launchCommand = new RelayCommand(LaunchCommandExecute, CanLaunchCommandExecute);
}
return launchCommand;
}
}
private void LaunchCommandExecute(object parameter){
//Do something to recognize the button.
//Could use ObservableCollection<Module> module_objects
//to match, if I could get the buttons content or name
}
private bool CanLaunchCommandExecute(object parameter){
return true;
}
However tried this in the MainViewModel as well hoping for a bubbling effect to occur, no such luck unfortunately.

You can do this by using a RoutedCommand instead of a RelayCommand.
Alternatively, your ViewModel could raise an event, to which the parent (MainViewModel) could subscribe. When the button is pressed, you could simply raise the event.

Change LaunchCommand to a dependency property and then bind that to your MainWindow's command in the XAML where you declare the LauncherView.

Related

Select All in DataGrid from button

I have a little problem about selecting all rows from a DataGrid.
The question is that the CommandButton is in a UserControl which refers to the MainViewModel and the grid is in a UserControl which refers to another ViewModel. The first thought was to make a static method and call it from the mvm, but the datagrid is not static and can't be called. How can i do?
Edit:
I actually solved the problem by rferencing a static instance of the grid from the code behind.
In the pushbutton viewmodel i recalled the select all method inside the command execution
if (_CommandBtnSelectAll == null)
{
// creo una nuova istanza del comando
_CommandBtnSelectAll = new USCommands(
item =>
{
ViewControlCodeBehind.grid.SelectAll();
}
}
In the ViewControl Code Behind i just had to set a static instance of a new DataGrid and assign it the actual datagrid.
public static DataGrid grid;
public ControlBody() // It's the UserControl initialization
{
this.grid = DataGridControl; // DataGridControl is the name of the actual control
}
You could send a message or an event from the view model and subscribe to and handle this event in the view using a messenger or an event aggregator. Please refer to this blog post for more information about this.
The other option would be to inject the view model with an interface that the view implements as suggested here.

How to be get propertychange event from Uc1 to Uc2 on one Windows (WPF)

I have a problem with WPF binding. I have frmBill.xaml Window with two usercontrol like that:
<materialDesign:TransitionerSlide OpeningEffect="{materialDesign:TransitionEffect SlideInFromRight}">
<materialDesign:TransitionerSlide.BackwardWipe>
<materialDesign:SlideWipe Direction="Right"/>
</materialDesign:TransitionerSlide.BackwardWipe>
<materialDesign:TransitionerSlide.ForwardWipe>
<materialDesign:SlideWipe Direction="Left"/>
</materialDesign:TransitionerSlide.ForwardWipe>
<local:ucBill_Information/>
</materialDesign:TransitionerSlide>
</materialDesign:Transitioner>
</Grid>
And I have two ViewModel as ucDeclarationViewModel.cs and ucBill_ViewModel for this. And using Thread with claim based to bind data between this by using a button click event (I got it), but I can not bind data from VM to V (Using prism). I know my issue, but I dont know how to solve it :(. Please help me.
private ucBill_InformationViewModel _ucBlvm;
public ucBill_InformationViewModel ucBIvm { get => _ucBlvm; set => _ucBlvm = value; }
public ucBill_Information()
{
InitializeComponent();
this.DataContext = ucBIvm;
}

Avalonia UI - How to get/set properties of UI controls from code

I have several TabControls defined in my XAML. I would like my ViewModel to be aware of the TabItem name or the index of the TabItem that is selected.
I also have a ScrollViewer that i would like to always scroll to the bottom when ever a button is pressed.
I should be able to solve both of the above issues if i could somehow get access to the elements in my code.
How can i acheve something like this:
var tabIndex = this.GetElement<TabControl>("NameOfSomeTabControl").SelectedIndex;
var scrollViewer = this.GetElement<ScrollViewer>("NameOfSomeScrollViewer");
scrollViewer.VerticalScrollBarValue = scrollViewer.VerticalScrollBarMaximum;
Edit: code for xaml, viewModel code
Edit 2:
Looks like i am able to get the instance of the element from the window class, however i'm still not sure how to pass the reference to the ViewModel.
Edit 3: I can achieve the scroll viewer going to the bottom automatically using the code below. however, once that method is invoked it seems like the scrolling gets disabled.
var tbRaw = this.Get<TextBlock>("tbRawOutput");
tbRaw.PropertyChanged += (s,e) => {
var svRaw = this.Get<ScrollViewer>("svRawOutput");
svRaw.Offset = new Vector(svRaw.Offset.X, svRaw.Extent.Height -svRaw.Viewport.Height);};
An easier way to do this might be to use the DataContextChanged event handler in your main Window class:
public MainWindow()
{
InitializeComponent();
DataContextChanged += (object sender, EventArgs wat) =>
{
// here, this.DataContext will be your MainWindowViewModel
};
}
Then you can attach more event handlers/use getters and setters on the view model from the Window

Adding UserControl to a Canvas by a Command in ViewModel in WPF

I have a UserControl say Stock and it has a Button called Display
<Button Command="{Binding DisplayCommand}" CommandParameter="StockGroups">Display</Button>
Now when i Click this button it should add an another UserControl named Display to the Canvas which is in HomeWindow and should pass the CommandParameter to the Display userControl.
private DelegateCommand<string> _displayCommand;
public virtual void DisplayExecuted(string param){}
public ICommand DisplayCommand
{
get
{
if (_displayCommand == null)
_displayCommand = new DelegateCommand<string>(new Action<string>(DisplayExecuted));
return _displayCommand;
}
}
An alternative method which is more MVVM-ish would be to have a boolean property named ShouldDisplayControl, which is then bound to the control's Visibility property (using the [BooleanToVisibilityConverter]) 1), while passing the CommandParameter as a second property, maybe ControlParameter, which the control is also bound to.
This is not an operation that should involve the ViewModel, since it does not manipulate any model data.
Instead of a ViewModel command, consider merely handling the button's OnClick in the code-behind of the xaml.
In your HomeWindow.xaml.cs file:
protected override void Display_OnClick(object sender, EventArgs e) {
var buttonName = ((Button)sender).Name; // gets the name of the button that triggered this event
var displayControl = new DisplayControl(); // your user control
displayControl.param = buttonName; // set the desired property on your display control to the name of the button that was clicked
((Canvas)Content).Children.Add(displayControl); // 'Content' is your Canvas element
}
And in your HomeWindow.xaml file:
<Button x:Name="StockGroups" Click="Display_OnClick" Text="Display" />
That should get you what you want, without needing to create and invoke a command in the viewmodel. The name of the clicked button will be set to the specified property in your userControl, and an instance of the control will be created inside the Canvas.

Binding a UserControl to a custom BusyIndicator control

I have a requirement to focus on a specific textbox when a new view is loaded.
The solution was to add this line of code to the OnLoaded event for the view:
Dispatcher.BeginInvoke(() => { NameTextBox.Focus(); });
So this worked for one view, but not another. I spent some time debugging the problem and realized that the new view I was working on had a BusyIndicator that takes focus away from all controls since the BusyIndicator being set to true and false was occuring after the OnLoaded event.
So the solution is to call focus to the NameTextBox after my BusyIndicator has been set to false. My idea was to create a reusable BusyIndicator control that handles this extra work. However, I am having trouble doing this in MVVM.
I started by making a simple extension of the toolkit:BusyIndicator:
public class EnhancedBusyIndicator : BusyIndicator
{
public UserControl ControlToFocusOn { get; set; }
private bool _remoteFocusIsEnabled = false;
public bool RemoteFocusIsEnabled
{
get
{
return _remoteFocusIsEnabled;
}
set
{
if (value == true)
EnableRemoteFocus();
}
}
private void EnableRemoteFocus()
{
if (ControlToFocusOn.IsNotNull())
Dispatcher.BeginInvoke(() => { ControlToFocusOn.Focus(); });
else
throw new InvalidOperationException("ControlToFocusOn has not been set.");
}
I added the control to my XAML file with no problem:
<my:EnhancedBusyIndicator
ControlToFocusOn="{Binding ElementName=NameTextBox}"
RemoteFocusIsEnabled="{Binding IsRemoteFocusEnabled}"
IsBusy="{Binding IsDetailsBusyIndicatorActive}"
...
>
...
<my:myTextBox (this extends TextBox)
x:Name="NameTextBox"
...
/>
...
</my:EnhancedBusyIndicator>
So the idea is when IsRemoteFocusEnabled is set to true in my ViewModel (which I do after I've set IsBusy to false in the ViewModel), focus will be set to NameTextBox. And if it works, others could use the EnhancedBusyIndicator and just bind to a different control and enable the focus appropriately in their own ViewModels, assuming their views have an intial BusyIndicator active.
However, I get this exception when the view is loaded:
Set property 'foo.Controls.EnhancedBusyIndicator.ControlToFocusOn' threw an exception. [Line: 45 Position: 26]
Will this solution I am attempting work? If so, what is wrong with what I have thus far (cannot set the ControlToFocusOn property)?
Update 1
I installed Visual Studio 10 Tools for Silverlight 5 and got a better error message when navigating to the new view. Now I gete this error message:
"System.ArgumentException: Object of type System.Windows.Data.Binding cannot be converted to type System.Windows.Controls.UserControl"
Also, I think I need to change the DataContext for this control. In the code-behind constructor, DataContext is set to my ViewModel. I tried adding a DataContext property to the EnhancedBusyIndicator, but that did not work:
<my:EnhancedBusyIndicator
DataContext="{Binding RelativeSource={RelativeSource Self}}"
ControlToFocusOn="{Binding ElementName=NameTextBox}"
RemoteFocusIsEnabled="{Binding IsRemoteFocusEnabled}"
IsBusy="{Binding IsDetailsBusyIndicatorActive}"
...
>
Update 2
I need to change UserControl to Control since I will be wanting to set focus to TextBox objects (which implement Control). However, this does not solve the issue.
#Matt, not sure
DataContext="{Binding RelativeSource={RelativeSource Self}}"
will work in Silverlight 5, have you tried binding it as a static resource?
Without a BusyIndicator present in the view, the common solution to solve the focus problem is to add the code
Dispatcher.BeginInvoke(() => { ControlToFocusOn.Focus(); });
to the Loaded event of the view. This actually works even with the BusyIndicator present; however, the BusyIndicator immediately takes focus away from the rest of the Silverlight controls. The solution is to invoke the Focus() method of the control after the BusyIndicator is not busy.
I was able to solve it by making a control like this:
public class EnhancedBusyIndicator : BusyIndicator
{
public EnhancedBusyIndicator()
{
Loaded += new RoutedEventHandler(EnhancedBusyIndicator_Loaded);
}
void EnhancedBusyIndicator_Loaded(object sender, RoutedEventArgs e)
{
AllowedToFocus = true;
}
private readonly DependencyProperty AllowedToFocusProperty = DependencyProperty.Register("AllowedToFocus", typeof(bool), typeof(EnhancedBusyIndicator), new PropertyMetadata(true));
public bool AllowedToFocus
{
get { return (bool)GetValue(AllowedToFocusProperty); }
set { SetValue(AllowedToFocusProperty, value); }
}
public readonly DependencyProperty ControlToFocusOnProperty = DependencyProperty.Register("ControlToFocusOn", typeof(Control), typeof(EnhancedBusyIndicator), null);
public Control ControlToFocusOn
{
get { return (Control)GetValue(ControlToFocusOnProperty); }
set { SetValue(ControlToFocusOnProperty, value); }
}
protected override void OnIsBusyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnIsBusyChanged(e);
if (AllowedToFocus && !IsBusy)
{
Dispatcher.BeginInvoke(() => { ControlToFocusOn.Focus(); });
AllowedToFocus = false;
}
}
}
To use it, replace the BusyIndicator tags in your xaml with the new EnhancedBusyIndicator and add the appropriate namespace.
Add a new property, ControlToFocusOn inside the element, and bind it to an existing element in the view that you want focus to be on after the EnhancedBusyIndicator disappears:
<my:EnhancedBusyIndicator
ControlToFocusOn="{Binding ElementName=NameTextBox}"
...
>
...
</my:EnhancedBusyIndicator>
In this case, I focused to a textbox called NameTextBox.
That's it. This control will get focus every time we navigate to the page. While we are on the page, if the EnhancedBusyIndicator becomes busy and not busy agiain, focus will not go to the control; this only happens on initial load.
If you want to allow the EnhancedBusyIndicator to focus to the ControlToFocusOn another time, add another property, AllowedToFocus:
<my:EnhancedBusyIndicator
ControlToFocusOn="{Binding ElementName=NameTextBox}"
AllowedToFocus="{Binding IsAllowedToFocus}"
...
>
...
</my:EnhancedBusyIndicator>
When AllowedToFocus is set to true, the next time EnhancedBusyIndicator switches from busy to not busy, focus will go to ControlToFocusOn.
AllowedToFocus can also be set to false when loading the view, to prevent focus from going to a control. If you bind AllowedToFocus to a ViewModel property, you may need to change the BindingMode. By default, it is OneTime.

Categories