I am using Caliburn, ninject
I want to create a plane Cockpit with a background image (inside grid)
and add dynamically (following the type of plane) lot of usercontrol (like lamps, switches or gauges at different position)
I know to add dynamically Usercontrol behind code (Grid.Add.Children)
but is it possible with Caliburn , using convention names and stay in MVVM? or I have to used code behind.. I have no idea to take the problem.
File: BootStrapper.cs
using System;
using System.Collections.Generic;
using System.Windows;
using Caliburn.Micro;
using Ninject;
using WpfApp2.ButtonAndSwitches;
namespace WpfApp2.Bootstrap
{
public class Bootstrapper : BootstrapperBase
{
private IKernel kernel;
public Bootstrapper()
{
Initialize();
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
}
protected override void Configure()
{
kernel = ServiceBootstrapper.Create();
kernel.Bind<IWindowManager>().To<WindowManager>().InSingletonScope();
kernel.Bind<MainWindowViewModel>().ToSelf().InSingletonScope();
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<MainWindowViewModel>();
}
private void OnSettingsLoaded()
{
}
protected override object GetInstance(Type service, string key)
{
return kernel.Get(service);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return kernel.GetAll(service);
}
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
kernel.Get<ILog>().Error(e.ExceptionObject as Exception);
}
}
public static class ServiceBootstrapper
{
public static IKernel Create()
{
var kernel = new StandardKernel();
BindCommon(kernel);
return kernel;
}
private static void BindCommon(StandardKernel kernel)
{
kernel.Bind<IEventAggregator>().To<EventAggregator>().InSingletonScope();
}
}
}
InstrumentsViewModel.cs
using Caliburn.Micro;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using WpfApp2.ButtonAndSwitches;
using WpfApp2.CommonEvents;
using WpfApp2.Extensions;
using IEventAggregator = WpfApp2.Events.IEventAggregator;
namespace WpfApp2
{
public class InstrumentsViewModel : Screen
{
private bool show;
private InstrumentsView instrumentsview;
private MainWindowViewModel mainwindow;
private readonly IEventAggregator eventAggregator;
private readonly int panel;
private readonly IWindowManager windowmanager;
private string _backgroundFile;
public InstrumentsViewModel(IEventAggregator eventAggregator, IWindowManager windowmanager, int panel)
{
this.panel = panel;
this.eventAggregator = eventAggregator;
this.windowmanager = windowmanager;
}
protected override void OnViewReady(object view)
{
if (panel == 0)
{
instrumentsview = view as InstrumentsView;
RenderOptions.ProcessRenderMode = System.Windows.Interop.RenderMode.SoftwareOnly;
var backgroundFilestring = #"F:\Ikarus-master\Ikarus\Images\Backgounds\A10CBackGround.png";
BackImage = (backgroundFilestring);
UserControl userControl = (UserControl)Activator.CreateInstance(Type.GetType("WpfApp2.ButtonAndSwitches.SwitchOn_Off_OnView"));
instrumentsview.MainGrid.Children.Add(userControl);
}
}
public string BackImage
{
get => _backgroundFile;
set
{
_backgroundFile = value;
NotifyOfPropertyChange(() => BackImage);
}
}
public void ButtonClose()
{
instrumentsview.Close();
}
public void BackImage_GotFocus()
{
}
public void Window_MouseMove()
{
}
public void Window_MouseDown()
{
}
public void Window_GotFocus()
{
}
}
}
Sample of Switch
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Caliburn.Micro;
using WpfApp2.CommonEvents;
namespace WpfApp2.ButtonAndSwitches
{
public class SwitchOn_Off_OnViewModel:Screen
{
private string pictureUp = "";
private string pictureMiddle = "";
private string pictureDown = "";
private int state = 1;
public SwitchOn_Off_OnViewModel()
{
SwitchMiddle = #"F:\Ikarus-master\Ikarus\Images\Switches\switch_mid2.png";
}
private string _Switchmiddle;
public string SwitchMiddle
{
get
{
return _Switchmiddle;
}
set
{
_Switchmiddle = value;
NotifyOfPropertyChange(() => SwitchMiddle);
}
}
public void Light_MouseWheel()
{
}
public void UpperRec_MouseDown()
{
if (++state > 2) state = 2;
SetValue(state, true);
}
public void UpperRec_MouseUp()
{
SetValue(1, true);
}
public void LowerRec_MouseDown()
{
if (--state < 0) state = 0;
SetValue(state, true);
}
public void LowerRec_MouseUp()
{
SetValue(1, true);
}
private void SetValue(int _state, bool _event, bool dontReset = false)
{
if (_state == 2)
{
}
if (_state == 1)
{
}
if (_state == 0)
{
}
}
}
}
Now all view associated
<Window x:Class="WpfApp2.MainWindowView"
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"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindowView" Height="158.241" Width="206.594" >
<Grid>
<Button x:Name="ShowOrCloseCockpit" Content="{Binding showorclose}" HorizontalAlignment="Left" Margin="28,29,0,0" VerticalAlignment="Top" Width="112" Height="50"/>
</Grid>
</Window>
<Window x:Class="WpfApp2.InstrumentsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="http://www.caliburnproject.org"
xmlns:controls="clr-namespace:WpfApp2.ButtonAndSwitches"
Title="Instruments"
Width="1920"
Height="1080"
AllowsTransparency="True"
Background="Transparent"
cal:Message.Attach="[Event GotFocus] = [Action Window_GotFocus()];[Event Mousedown] = [Action Window_MouseDown()];[Event MouseMove] = [Action Window_MouseMove()]"
Topmost="True"
WindowStyle="None">
<Border Background="Transparent"
BorderBrush="#00FFA500"
BorderThickness="0"
CornerRadius="0">
<Grid x:Name="MainGrid"
Background="#00060606"
Opacity="1.0">
<Image x:Name="BackImage" cal:Message.Attach="[Event GotFocus] = [Action BackImage_GotFocus()]"
Margin="0,0,0,0"
Stretch="Fill" />
<Button x:Name="btCloseRight"
cal:Message.Attach="[Event Click] = [Action ButtonClose()]"
Width="25"
Height="24"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Content="X"
Foreground="#FF190301"
RenderTransformOrigin="0.895,7.292">
<Button.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="#FFF3F3F3" />
<GradientStop Offset="0.5" Color="#FFEBEBEB" />
<GradientStop Offset="0.5" Color="#FFDDDDDD" />
<GradientStop Offset="1" Color="#FFAFAC68" />
</LinearGradientBrush>
</Button.Background>
</Button>
<TextBlock x:Name="MousePosition" HorizontalAlignment="Left" Margin="300,0,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Foreground="#FFFBF8F8"/>
</Grid>
</Border>
</Window>
and the Switch View
<UserControl x:Class="WpfApp2.ButtonAndSwitches.SwitchOn_Off_OnView"
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="http://www.caliburnproject.org"
mc:Ignorable="d"
d:DesignHeight="150" d:DesignWidth="70" cal:Message.Attach="[Event MouseWheel]=[Light_MouseWheel]">
<Grid Name="Switch" RenderTransformOrigin="0.5,0.5">
<Image x:Name="SwitchUp"
Width="70"
Height="150"
HorizontalAlignment="Left"
VerticalAlignment="Top" />
<Image x:Name="SwitchMiddle"
Width="70"
Height="150"
HorizontalAlignment="Left"
VerticalAlignment="Top" />
<Image x:Name="SwitchDown"
Width="70"
Height="150"
HorizontalAlignment="Left"
VerticalAlignment="Top" />
<Rectangle x:Name="UpperRec" cal:Message.Attach="[Event Mousedown]=[UpperRec_MouseDown];[Event MouseUP]=[UpperRec_MouseUp]"
Width="60"
Height="50"
Margin="5,20,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Fill="#00EEDB21"
Stroke="#00000000" />
<Rectangle x:Name="LowerRec" cal:Message.Attach="[Event Mousedown]=[LowerRec_MouseDown];[Event MouseUP]=[LowerRec_MouseUp]"
Width="60"
Height="50"
Margin="5,80,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Fill="#00EEDB21"
Stroke="#00000000"/>
<Rectangle x:Name="DesignFrame"
Width="70"
Height="150"
Margin="0,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Fill="#00EEDB21"
Stroke="#FFF38501"
StrokeThickness="4" />
</Grid>
</UserControl>
when I click on button from Mainview, i launch via windowsManager the instrumentsView
the problem is to add dynamically the switch (more then) onside the view instruments... how I could load the usercontrol and bind with the view (Caliburn??)
Sample of background image
Sample of Switch
Related
https://stackoverflow.com/a/36192552/9387175
In this answer the user suggests that the comboBoxAdaptor can be used to add an item to a combo box even if it does not exist in the item source. I do in fact see that it is working in the code, but I can't figure out why it refuses to display. The normal combo box functions correctly in the below example, the comboBoxAdaptor is not visible. Am I missing something like styles or templates? I can't seem to find the right combination.
My xaml:
<Window x:Class="WpfApp1.MainWindow"
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"
xmlns:adapters="clr-namespace:WpfApp1.Adapters"
mc:Ignorable="d"
Title="MainWindow"
Height="200"
Width="650">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="210" />
<ColumnDefinition Width="210" />
</Grid.ColumnDefinitions>
<adapters:ComboBoxAdaptor Grid.Column="0"
AllowNull="False"
Height="80"
ItemsSource="{Binding Path=DataEntries}"
SelectedItem="{Binding Path=DataEntry}">
<ComboBox Height="80" />
</adapters:ComboBoxAdaptor>
<ComboBox Grid.Column="1"
Height="80"
ItemsSource="{Binding Path=DataEntries}"
SelectedItem="{Binding Path=DataEntry}"
DisplayMemberPath="Name"
SelectedValuePath="Name" />
</Grid>
</Window>
My Code:
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
SampleViewModel vm = new SampleViewModel();
DataContext = vm;
}
}
public class SampleDataClass
{
public string Name { get; set; }
public SampleDataClass(string name)
{
Name = name;
}
}
public class SampleViewModel : INotifyPropertyChanged
{
private readonly IList<SampleDataClass> _dataEntries;
private string _dataEntry;
public event PropertyChangedEventHandler PropertyChanged;
public SampleViewModel()
{
IList<SampleDataClass> list = new List<SampleDataClass>();
list.Add(new SampleDataClass("tools"));
list.Add(new SampleDataClass("set"));
list.Add(new SampleDataClass("sort"));
list.Add(new SampleDataClass("flap"));
_dataEntries = list;
}
public IList<SampleDataClass> DataEntries
{
get { return _dataEntries; }
}
public string DataEntry
{
get
{
return _dataEntry;
}
set
{
if (_dataEntry == value) {return;}
_dataEntry = value;
OnPropertyChanged("DataEntry");
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Turns out that I was missing the style that links the ComboBox to the content of the ContentControl (ComboBoxAdaptor)
Style Example
<Style TargetType="adapters:ComboBoxAdaptor">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="adapters:ComboBoxAdaptor">
<ContentPresenter Content="{TemplateBinding ComboBox}"
Margin="{TemplateBinding Padding}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Is there any way to update listbox items text after updating it in textboxes? I wanna do it only with bindings if it is possible. Listbox is reading from list, but list isn't updating so it's never gonna change, unless I add new item to list. Here is code
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DockPanel VerticalAlignment="Top" HorizontalAlignment="Left">
<Button Name="dodaj" Width="40" Margin="5" Click="dodaj_Click">Dodaj</Button>
<Button Name="usun" Width="40" Margin=" 5" Click="usun_Click">Usuń</Button>
</DockPanel>
<DockPanel HorizontalAlignment="Left" VerticalAlignment="Center" DataContext="{Binding ElementName=lista, Path=osoba, Mode=TwoWay}">
<ListBox Name="lb" ItemsSource="{Binding}" Width="200" Height="230">
</ListBox>
</DockPanel>
<DockPanel HorizontalAlignment="Right" VerticalAlignment="top">
<WrapPanel>
<StackPanel>
<Label>Imię</Label>
<Label>Nazwisko</Label>
<Label>Email</Label>
<Label>Telefon</Label>
</StackPanel>
<StackPanel DataContext="{Binding ElementName=lb, Path=SelectedItem, UpdateSourceTrigger=LostFocus}" TextBoxBase.TextChanged="zmiana">
<TextBox Width="200" Margin="3" Name="imie" Text="{Binding Path=imie}"></TextBox>
<TextBox Width="200" Name="nazwisko" Text="{Binding Path=nazwisko}"></TextBox>
<TextBox Width="200" Margin="3" Name="email" Text="{Binding Path=email}"></TextBox>
<TextBox Width="200" Name="telefon" Text="{Binding Path=telefon}"></TextBox>
</StackPanel>
</WrapPanel>
</DockPanel>
</Grid>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
lista.Add(new dane("imie1", "nazwisko1", "email1", "tel1"));
lista.Add(new dane("imie2", "nazwisko2", "email2", "tel2"));
lb.DataContext = lista;
}
public class dane : INotifyPropertyChanged
{
public dane(string imie, string nazwisko, string email, string telefon)
{
this._imie = imie;
this.nazwisko = nazwisko;
this.osoba = nazwisko + " " + imie;
this.email = email;
this.telefon = telefon;
}
private string _imie;
public string imie
{
set
{
_imie = value;
OnPropertyChanged("Imie");
}
get
{
return _imie;
}
}
public string nazwisko { set; get; }
public string osoba { set; get; }
public string email { set; get; }
public string telefon { set; get; }
public override string ToString()
{
return osoba;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string value)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(value));
}
}
}
public ObservableCollection<dane> lista = new ObservableCollection<dane>();
//public List<dane> lista = new List<dane>();
/*public ObservableCollection<dane> lista
{
get { return (ObservableCollection<dane>)GetValue(listaProperty); }
set { SetValue(listaProperty, value); }
}
public static readonly DependencyProperty listaProperty =
DependencyProperty.Register("lista", typeof(ObservableCollection<dane>), typeof(Window), new UIPropertyMetadata(null));
*/
private void dodaj_Click(object sender, RoutedEventArgs e)
{
lista.Add(new dane("...", "", "", ""));
MessageBox.Show(lista[0].ToString());
}
private void usun_Click(object sender, RoutedEventArgs e)
{
lista.RemoveAt(lb.SelectedIndex);
}
}
}
Listbox shows instances of your dane class by their ToString() representation. You should bind listbox to osoba property directly with DisplayMemberPath. And also value of osoba property is not udated then you change imia and nazwisko. You should recalculate it on its getter and also raise PropertChanged("osoba") then properties nazwisko or imia are being changed.
i have made a template that look like this :
<ControlTemplate x:Key="onoffValue" TargetType="{x:Type Control}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Height="20" Margin="0,5,0,0">
<RadioButton Content="On" Height="20" Name="On_radiobutton" />
<RadioButton Content="Off" Height="20" Name="Off_radiobutton" Margin="20,0,0,0" />
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=BootSector}" Value="true">
<Setter TargetName="On_radiobutton" Property="IsChecked" Value="true"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=BootSector}" Value="false">
<Setter TargetName="Off_radiobutton" Property="IsChecked" Value="true"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
For now, it is bind to the property BootSector(bool) ofa "Configuration" object.
I use this template in my window that has a configuration object as data context like this :
<Control Template="{StaticResource onoffValue}">
</Control>
It works great, but i want to go further.
I would like to know how i can pass a different property to my template to dynamically bind (dynamically change the property the template is bind to)
ie i tryed something like
<Control Template="{StaticResource onoffValue}" xmlns:test="{Binding Path=BootSector}"/>
and bind it in the template to "test" but it doesn't work
Is it possible ? How can i do that ? I think i'm not too far away but not there still !
Thank you in advance
Edit : Concerning Dmitry answer :
There is a bug using that. When i do :
<StackPanel local:ToggleControl.IsOn="{Binding BootSector, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal" HorizontalAlignment="Center" Margin="5">
<RadioButton Content="On" local:ToggleControl.Role="On" Height="20" Margin="5" />
<RadioButton Content="Off" local:ToggleControl.Role="Off" Height="20" Margin="5" />
</StackPanel>
By default BootSector is on false. When i click on the on button (true), it sets bootSector to true and then immediately to false . The behaviour should be that it stays to true until it is unchecked ? Is this related to the problem related here ? http://geekswithblogs.net/claraoscura/archive/2008/10/17/125901.aspx
Here, the idea is - generic behaviors are never complex and generally not worth creating a custom control. I undertand that implmentation may vary, but the approach will remain the same. It makes sense to use XAML for the parts which can change and code for the stuff which will remain constant.
UPDATE 1- It's getting even easier when using Custom controls. You won't need attached property no more - as you'll get a dedicated space for it inside your custom control, also, you can use x:Name and GetTemplateChild(..) to otain a reference to individual RadioButtons.
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace RadioButtons
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += (o, e) =>
{
this.DataContext = new TwoBoolean()
{
PropertyA = false,
PropertyB = true
};
};
}
private void Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(((TwoBoolean)this.DataContext).ToString());
}
}
public enum RadioButtonRole
{
On,
Off
}
public class ToggleControl : DependencyObject
{
public static readonly DependencyProperty IsOnProperty =
DependencyProperty.RegisterAttached("IsOn",
typeof(bool?),
typeof(ToggleControl),
new PropertyMetadata(null,
new PropertyChangedCallback((o, e) =>
{
ToggleControl.OnIsOnChanged((Panel)o, (bool)e.NewValue);
})));
public static readonly DependencyProperty RoleProperty =
DependencyProperty.RegisterAttached("Role",
typeof(RadioButtonRole?),
typeof(ToggleControl),
new PropertyMetadata(null,
new PropertyChangedCallback((o, e) =>
{
})));
private static readonly DependencyProperty IsSetUpProperty =
DependencyProperty.RegisterAttached("IsSetUp",
typeof(bool),
typeof(ToggleControl),
new PropertyMetadata(false));
private static void OnIsOnChanged(Panel panel, bool e)
{
if (!ToggleControl.IsSetup(panel))
{
ToggleControl.Setup(panel);
}
RadioButtonRole role;
if (e)
{
role = RadioButtonRole.On;
}
else
{
role = RadioButtonRole.Off;
}
ToggleControl.GetRadioButtonByRole(role, panel).IsChecked = true;
}
private static void Setup(Panel panel)
{
// get buttons
foreach (RadioButton radioButton in
new RadioButtonRole[2]
{
RadioButtonRole.On,
RadioButtonRole.Off
}.Select(t =>
ToggleControl.GetRadioButtonByRole(t, panel)))
{
radioButton.Checked += (o2, e2) =>
{
RadioButton checkedRadioButton = (RadioButton)o2;
panel.SetValue(ToggleControl.IsOnProperty,
ToggleControl.GetRadioButtonRole(checkedRadioButton) == RadioButtonRole.On);
};
}
panel.SetValue(ToggleControl.IsSetUpProperty, true);
}
private static bool IsSetup(Panel o)
{
return (bool)o.GetValue(ToggleControl.IsSetUpProperty);
}
private static RadioButton GetRadioButtonByRole(RadioButtonRole role,
Panel container)
{
return container.Children.OfType<RadioButton>().First(t =>
(RadioButtonRole)t.GetValue(ToggleControl.RoleProperty) == role);
}
private static RadioButtonRole GetRadioButtonRole(RadioButton radioButton)
{
return (RadioButtonRole)radioButton.GetValue(ToggleControl.RoleProperty);
}
public static void SetIsOn(DependencyObject o, bool? e)
{
o.SetValue(ToggleControl.IsOnProperty, e);
}
public static bool? GetIsOn(DependencyObject e)
{
return (bool?)e.GetValue(ToggleControl.IsOnProperty);
}
public static void SetRole(DependencyObject o, RadioButtonRole? e)
{
o.SetValue(ToggleControl.RoleProperty, e);
}
public static RadioButtonRole? GetRole(DependencyObject e)
{
return (RadioButtonRole?)e.GetValue(ToggleControl.RoleProperty);
}
}
public class TwoBoolean: INotifyPropertyChanged
{
private bool propertyA, propertyB;
public bool PropertyA
{
get
{
return this.propertyA;
}
set
{
this.propertyA = value;
this.OnPropertyChanged("PropertyA");
}
}
public bool PropertyB
{
get
{
return this.propertyB;
}
set
{
this.propertyB = value;
this.OnPropertyChanged("PropertyB");
}
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
public override string ToString()
{
return string.Format("PropertyA:{0}, PropertyB:{1}", this.PropertyA, this.PropertyB);
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Markup:
<Window x:Class="RadioButtons.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:RadioButtons"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Margin="5" VerticalAlignment="Center">PropertyA</TextBlock>
<StackPanel local:ToggleControl.IsOn="{Binding PropertyA, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Grid.Row="0" Grid.Column="1"
Orientation="Horizontal" HorizontalAlignment="Center" Margin="5">
<RadioButton Content="On" local:ToggleControl.Role="On" Height="20" Margin="5" />
<RadioButton Content="Off" local:ToggleControl.Role="Off" Height="20" Margin="5" />
</StackPanel>
<TextBlock Grid.Row="1" Grid.Column="0" Margin="5" VerticalAlignment="Center">PropertyB</TextBlock>
<StackPanel local:ToggleControl.IsOn="{Binding PropertyB, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Grid.Row="1" Grid.Column="1"
Orientation="Horizontal" HorizontalAlignment="Center" Margin="5">
<RadioButton Content="On" local:ToggleControl.Role="On" Height="20" Margin="5" />
<RadioButton Content="Off" local:ToggleControl.Role="Off" Height="20" Margin="5" />
</StackPanel>
<Button Click="Button_Click" Grid.Row="3" Grid.ColumnSpan="2">Save</Button>
</Grid>
</Window>
You should not use an xmlns to pass a parameter, rather use the Tag or template a ContentControl, then you can bind the Content to your property (set it to TwoWay) and use a TemplateBinding to Content inside the template.
i have application with two Tabs. on first tab placed button which sets the current position in dataGrid1 on second tab. While i won't show a second tab, i can't set current position by button1.
<UserControl x:Class="SilverlightApplication9.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"
d:DesignHeight="300" d:DesignWidth="400" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
<Grid x:Name="LayoutRoot" Background="White">
<sdk:TabControl Height="234" HorizontalAlignment="Left" Margin="52,44,0,0" Name="tabControl1" VerticalAlignment="Top" Width="326">
<sdk:TabItem Header="tabItem1" Name="tabItem1">
<Grid>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="74,44,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</sdk:TabItem>
<sdk:TabItem Header="tabItem2" Name="tabItem2">
<Grid>
<sdk:DataGrid ItemsSource="{Binding strs}" RowBackground="White" AutoGenerateColumns="False" Height="141" HorizontalAlignment="Left" Margin="36,12,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="199">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Binding="{Binding}" CanUserReorder="True" CanUserResize="True" CanUserSort="True" Width="Auto" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</Grid>
</sdk:TabItem>
</sdk:TabControl>
</Grid>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
namespace SilverlightApplication9
{
public partial class MainPage : UserControl
{
private ObservableCollection<string> _strs = new ObservableCollection<string>();
public ObservableCollection<string> strs { get { return _strs; } set { _strs = value; } }
public MainPage()
{
this.DataContext = this;
InitializeComponent();
strs.Add("1");
strs.Add("2");
strs.Add("3");
strs.Add("4");
strs.Add("5");
}
private void button1_Click(object sender, RoutedEventArgs e)
{
dataGrid1.SelectedIndex = 2;
}
}
}
The problem is that the DataGrid isn't loaded when you attempt to set the SelectedIndex in the button click handler if you haven't already navigated to the tab that contains the DataGrid.
The way to achieve what you want is to use data binding. You will also want to implement INotifyPropertyChanged for any subsequent changes to the property you bind DataGrid.SelectedIndex to. The following is a rough example of how to do what you want in the code you provided.
public partial class MainPage : UserControl, INotifyPropertyChanged
{
private ObservableCollection<string> _strs
= new ObservableCollection<string>();
public ObservableCollection<string> strs
{
get { return _strs; }
set { _strs = value; }
}
public MainPage()
{
this.DataContext = this;
InitializeComponent();
strs.Add("1");
strs.Add("2");
strs.Add("3");
strs.Add("4");
strs.Add("5");
SelectedIndex = 0;
}
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
_selectedIndex = value;
var pChanged = PropertyChanged;
if (pChanged != null)
pChanged(this, new PropertyChangedEventArgs("SelectedIndex"));
}
}
private void button1_Click(object sender, RoutedEventArgs e)
{
SelectedIndex ++;
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then update your DataGrid definition in xaml to:
<sdk:DataGrid ItemsSource="{Binding strs}"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
RowBackground="White"
AutoGenerateColumns="False"
Height="141"
HorizontalAlignment="Left"
Margin="36,12,0,0"
Name="dataGrid1"
VerticalAlignment="Top"
Width="199">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn Binding="{Binding}"
CanUserReorder="True"
CanUserResize="True"
CanUserSort="True"
Width="Auto" />
</sdk:DataGrid.Columns>
</sdk:DataGrid>
How do you make a Button call ICommand.CanExecute when the command parameter is changed?
This is my current XAML.
<Button Content="Delete" Command="{Binding DeleteItemCommand}" CommandParameter="{Binding SelectedItem, ElementName=DaGrid}" />
EDIT It appears this is only an issue in WPF.
I'm not sure what you're doing wrong, but here is an example of a Button being controlled both by a BindingParameter and a CanExecute Flag. Perhaps your binding parameter isn't a DependencyProperty, and therefore, when it changes the Button isn't being notified.
<UserControl x:Class="SilverlightICommandTest.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"
xmlns:ct="clr-namespace:SilverlightICommandTest"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<ct:TestModel x:Key="Model" />
</UserControl.Resources>
<StackPanel x:Name="LayoutRoot" Orientation="Vertical" Background="White" DataContext="{StaticResource Model}">
<CheckBox Content="Enable" IsChecked="{Binding TestCmd.CanDoCommand, Mode=TwoWay}" />
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ElementName=testSlider, Path=Value}" Width="40" Grid.Column="0" />
<Slider Name="testSlider" Minimum="0" Maximum="100" SmallChange="1" Grid.Column="1" />
</Grid>
<Button Command="{Binding TestCmd}" CommandParameter="{Binding ElementName=testSlider, Path=Value}" Content="Do Something" />
</StackPanel>
</UserControl>
And the code file:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SilverlightICommandTest
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
}
public class TestModel : DependencyObject
{
TestCommand _testCmd = new TestCommand();
public TestCommand TestCmd { get { return _testCmd; } }
public TestModel()
{
}
}
public class TestCommand : DependencyObject, ICommand
{
public static readonly DependencyProperty CanDoCommandProperty = DependencyProperty.Register("CanDoCommand", typeof(Boolean), typeof(TestCommand), new PropertyMetadata(false, new PropertyChangedCallback(CanDoCommandChanged)));
public Boolean CanDoCommand
{
get { return (Boolean)GetValue(CanDoCommandProperty); }
set { SetValue(CanDoCommandProperty, value); }
}
public event EventHandler CanExecuteChanged;
public TestCommand()
{
}
public Boolean CanExecute(Object parameter)
{
return this.CanDoCommand && (((Int32)(Double)parameter) % 2 == 0);
}
public void Execute(Object parameter)
{
MessageBox.Show("Oh Hai!");
}
private void OnCanDoCommandChanged(DependencyPropertyChangedEventArgs args)
{
if (this.CanExecuteChanged != null)
{
this.CanExecuteChanged(this, new EventArgs());
}
}
private static void CanDoCommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
((TestCommand)sender).OnCanDoCommandChanged(args);
}
}
}
In the future I recommend doing a little more research on the pattern first (http://www.silverlightshow.net/items/Model-View-ViewModel-in-Silverlight.aspx), and if you still can't figure it out, post more of your source code.
Strange. Normally OnCommandParameterChanged calls UpdateCanExecute (both internal methods). Does the Binding to CommandParameter work as expected?
You need to call CommandManager.InvalidateRequerySuggested to re-evaluate CanExecute. Note that it will re-evaluate it for all commands, not just the one your want...