Hy,
I have a menu with a few menu items. I have various other elements like a treeview and some controls. When I open the program all elements in the menu are available. But the first step I have to do is to connect to the server. So all the the other elements shouldn't available till there is made a connection via the connection menu item.
Then I want to show only menu items if a special tree view (for instance the whole item structure) item is choosen for instance all topics. For instance there should be special menu items available if I click a treeview entry in the menu.
Is it possible to accomplish this in xaml?
Update1:
MainWindow.xaml
Title="Service Bus Visualizer" Height="680" Width="1200" Name="Root"
<MenuItem Header="_Read File" Name="readFile" Click="MenuItemReadFile" HorizontalAlignment="Right" Width="187" IsEnabled="{Binding Path=DataContext.IsMonitoring, ElementName=Root}">
<MenuItem.Icon>
<Image Source="Icons/Open.ico" Width="16" Height="16" />
</MenuItem.Icon>
</MenuItem>
MainWindow.xaml.cs
public bool IsMonitoring
{
get
{
return isMonitoring;
}
set
{
isMonitoring = value;
RaisePropertyChanged("IsMonitoring");
}
}
private bool isMonitoring;
public MainWindow()
{
InitializeComponent();
this.IsMonitoring = false;
this.DataContext = this;
Application.Current.MainWindow = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
ConnectionWindow.xaml.cs
MainWindow mainWindow = Application.Current.MainWindow as MainWindow;
mainWindow.IsMonitoring = true;
I get no error on the output window but it doesn't work?
Update2:
I have a second parameter which is a ObservableCollection.
MainWindow.xaml
<ListBox Grid.Row="3" Name="Logger" ItemsSource="{Binding Path=DataContext.LoggingList, ElementName=Root}" DisplayMemberPath="Message" IsSynchronizedWithCurrentItem="True" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Visible" SelectionChanged="BringSelectionIntoView">
</ListBox>
MainWindow.xaml.cs
public static ObservableCollection<Log> LoggingList { get; set; }
public MainWindow()
{
LoggingList = new ThreadSafeObservableCollection<Log>();
this.IsMonitoring = false;
this.DataContext = this;
Application.Current.MainWindow = this;
InitializeComponent();
}
Log.cs
public class Log : INotifyPropertyChanged
{
public string Message {
get
{
return message;
}
set
{
message = value;
NotifyPropertyChanged("Message");
}
}
public string message;
public Log()
{
}
public Log(string message)
{
this.Message = message;
NotifyPropertyChanged("Message");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
Best regards
First, you have two options as far as "availability" is concerned. The "IsEnabled" property, and the "Visible" property. "IsEnabled" is a bool, and determines if the user can click/select/interact with a given element. Generally speaking, if this property is set to false the element will appear "greyed out".
Changing Visibility will make the element appear/disappear entirely. When set to "Visible" (this is actually an enum), it appears normally. When set to "Hidden", the space for it is reserved on the UI, but you can't actually see it. When set to "Collapsed" you cannot see it and no space is reserved for it in the layout.
For your first requirement (waiting to connect to the server), I would use IsEnabled bound to a "IsConnected" property like so:
IsEnabled="{Binding IsConnected}"
That would go on each item that needs to have this behavior.
The "context-specific" menu items are a bit more complicated, but the basic idea would be a binding on Visible for each of the context sensitive items like:
Visible="{Binding Path=SelectedItem, ElementName=MyTreeView, Converter={StaticResource SelectedItemToVisibilityConverter}, ConverterParameter={x:Type ChildItem}"
I am assuming that each items visibility depends on what type of item is selected (child or parent), you should be able to extend the example if I was wrong. The converter would then look like:
public class SelectedItemToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ((parameter as Type).IsInstanceOfType(value))
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(...)
{
return Binding.DoNothing;
}
}
Please let me know if I can clarify anything. Hopefully that gives you a good starting point for what you are trying to do.
Update:
Looking at your code I see a couple potential problems:
IsMonitoring is declared as a public field. Binding only works with public properties. This property needs to raise the PropertyChanged event for it to work.
In "MainWindow.xaml.cs" you are setting the DataContext multiple times. This isn't how DataContext works in WPF. You need to set it to one object (your ViewModel) that contains all the properties you are interested in binding to. While it is considered bad practice, you could write this.DataContext = this to get it working before you build a ViewModel class.
The IsMonitoring field is declared in your "MainWindow.xaml.cs" file. First, this should be in a view model. Second, the binding is looking for that property on the MenuItem class (likely because it is in some sort of ItemsControl). If you want it on the root data context, give your window some name (like "Root") and use the following binding:
"{Binding Path=DataContext.IsMonitoring, ElementName=Root}"
Hopefully that makes sense. Let me know if I can help further!
Related
I am quiet new to programming and am currently learning C# and the MVVM pattern.
I need to code a database tool for ChiliPlants for university.
There you should be able to add a new object to an ObservableCollection.
To add a new Item to this ObservableCollection a new Window opens. It looks like this:
Window Add
I now want the two RadioBoxes to be bound to a property called "HybridSeed". Which is defined in the ViewModel:
//Public Property HybridSeed
public bool HybridSeed
{
get { return ChiliModel.HybridSeed; }
set
{
if (ChiliModel.HybridSeed == value)
return;
ChiliModel.HybridSeed = value;
OnPropertyChanged("HybridSeed");
}
}
The RadioBox part of my View looks like this:
<RadioButton Grid.Row="5" Content="Ja" Grid.Column="1" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top"/>
<RadioButton Grid.Row="5" Content="Nein" Grid.Column="1" HorizontalAlignment="Left" Margin="89,10,0,0" VerticalAlignment="Top"/>
But how to bind the outcome of a user clicking on these RadioButtons to this HybridSeed Property? Important is that the outcome is a bool.
I looked up almost every entry similar to this topic, but I did not find a simple solution. Or a solution which I was able to understand with my bad coding skills :( ...
I would be very happy if you guys could help me. Please keep it simple for this newbie :)
If there is a simpler solution using a CheckBox or a ComboBox it would also be perfect. The most important thing is to have a nice user interface. Right now it only works with a TextBox where the user always has to write "True" or "False".
Solution:
I added the IsClicked Property in the "Yes" RadioButton to be bound to my boulean property with: IsClicked="{Binding HybridSeed}". Thanks to naslund for his fast answer :)
Just bind HybridSeed to the Yes-radiobutton. It will then either be true if the user has selected that or false if No-radiobutton has been selected (or if nothing has been selected). Binding to both buttons in this case is a bit redundant since the mechanism of radiobuttons takes care of it.
WPF:
<RadioButton Content="Yes" IsChecked="{Binding HybridSeed}" />
<RadioButton Content="No" />
<Label Content="{Binding HybridSeed}" ContentStringFormat="Value is: {0}" />
Logic:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel : INotifyPropertyChanged
{
private bool hybridSeed;
public bool HybridSeed
{
get { return hybridSeed; }
set
{
hybridSeed = value;
OnPropertyChanged(nameof(HybridSeed));
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I am very new to the concept of data binding and I don't think I understood it completely. I have a class named Project with a LinkedList of type ToDo as one of its properties. When I navigate to one instance of Project, I will display the LinkedList of type ToDo in a ListView. I have created functions that allow me to change the sequences of the nodes in the LinkedList (move up, move down) and to remove the selected node (delete). I want the ListView to refresh whenever there is a change in the LinkedList, (move up, move down or delete). However, I cannot achieve that. Here is my code: (not all parts are included)
XAML of the page:
<ListView x:Name="myListView" ItemsSource="{Binding Source={StaticResource ToDos}, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox x:Name="myCheckBox"
Content="{Binding ToDoTitle, Mode=TwoWay}"
IsChecked="{Binding IsCompleted, Mode=TwoWay}">
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
C# for DataModel:
public class ToDo : INotifyPropertyChanged
{
private string toDoTitle;
private bool isCompleted;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public string ToDoTitle { get { return this.toDoTitle; } set { this.toDoTitle = value; this.OnPropertyChanged(); } }
public bool IsCompleted { get { return this.isCompleted; } set { this.isCompleted = value; this.OnPropertyChanged(); } }
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Projects : INotifyPropertyChanged
{
private LinkedList<ToDo> toDos;
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public LinkedList<ToDo> ToDos { get { return this.toDos; } set { this.toDos = value; this.OnCollectionChanged(); } }
public Projects()
{
ToDos = new LinkedList<ToDo>();
}
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
// Raise the PropertyChanged event, passing the name of the property whose value has changed.
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Thank you.
First I would advise you to read about MVVM, and try to follow some basic tutorials like this one.
You can use MVVM Light to avoid managing the INotifyPropertyChanged by yourself at first (but it's really good to know how MVVM light work under the hood).
To come back to your problem, your current code notifies only if you set the full ToDos list. If you want to be aware of any change in a list (seing when an item is add/remove/update), you are probably looking for an ObservableCollection, not a LinkedList.
Hope it helps.
I have a panel and my idea is to have it populated by a stack panel containing two text boxes. When the user enters something in the left box, something should be generated in the right one, as follows.
<StackPanel Orientation="Horizontal">
<TextBox Name="Lefty" LostFocus="FillMyBuddy" />
<TextBox Name="Righty" LostFocus="FillMyBuddy" />
</StackPanel>
However, I'd like to add an option to add/remove rows and, since I wish not to limit myself to the number of such, I get a bit uncertain regarding the approach on two points.
Manipulating DOM (well, it's XAML/WPF but you see what I'm aiming at).
Event handling.
Is it a big no-no to programmatically affect the mark-up structure of the window? Or is it OK to add/remove panels during run-time?
What would the recommended way to be if I want the Lefty number 3 change stuff in Righty number 3? Anything more neat than checking the sender and pulling its siblings from the parent? I want to use a single event handler for any and all rows (knowing that the operations are always intra-row-wise).
You will want to follow MVVM, and have no code in your code-behind (programmatically affect the mark-up structure) files. The concept is easy when you grasp it, so learn it before you start writing your code.
In short, you are going to want to have a view model (something that implements INotifyPropertyChanged (INPC)) which holds your collection of items (which are going to be models, or view models in pure-MVVM). In "hybrid"-MVVM you could just have your models implement INPC.
Then, through the use of commands, you'd implement the logic to remove items from the list that its in. You can pass references, raise notification, using event bubbling, etc. (it's your preference) to have the item actually removed. In my case, I just passed a "manager" to the hybrid-model and held a reference to that. When the command is called (button is clicked), the model calls for the reference to remove itself from the list.
After you do that you define a DataTemplate to define what an "item" should look like one the View. You use a ItemsControl to show a collection of items, and bind to its ItemsSource so the collection of items are shown. Set your ItemsControl.ItemTemplate to the DataTemplate you created, and anything added to the collection bound to ItemsSource of the type defined in DataTemplate.DataType will render as you specify in the DataTemplate.
At the end of the day, you should learn about MVVM design, DataContext, INPC, Commands, Control types and their "main" properties, e.g. everything that inherits from ItemsControl has an ItemsSource property.
Here is a working example, where changing the original string, will reverse it and put it in the read-only right side text box:
MainWindow.xaml.cs (code-behind)
public partial class MainWindow : Window
{
StructureVm _struct = new StructureVm("Test");
public MainWindow()
{
InitializeComponent();
DataContext = _struct;
}
}
MainWindow.xaml (View)
<Window x:Class="DataTemplateWithCommands.MainWindow"
xmlns:local="clr-namespace:DataTemplateWithCommands"
Title="MainWindow" Height="350" Width="525" Background="Orange">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Model}"
x:Key="VmItem">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Original, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Encoded}"
IsReadOnly="True" />
<Button Content="X"
Command="{Binding RemoveMeCommand}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Items}"
ItemTemplate="{StaticResource VmItem}">
</ItemsControl>
</Grid>
</Window>
Interface (helpful for Dependency Injection)
public interface IStructureManager
{
bool RemoveItem(Model itemToRemove);
}
ViewModel
public class StructureVm : IStructureManager
{
private readonly ObservableCollection<Model> _items;
private readonly string _title;
public StructureVm(string title)
{
_title = title;
_items = new ObservableCollection<Model>
{
new Model(this, "12"),
new Model(this, "23"),
new Model(this, "34"),
new Model(this, "45"),
new Model(this, "56"),
new Model(this, "67"),
new Model(this, "78"),
new Model(this, "89"),
};
}}
public ObservableCollection<Model> Items
{
get
{
return _items;
}
}
public string Title
{
get
{
return _title;
}
}
public bool RemoveItem(Model itemToRemove)
{
return _items.Remove(itemToRemove);
}
}
Model (not pure-MVVM, pure MVVM models don't implement INPC, and don't have Command in them)
public class Model : INotifyPropertyChanged
{
private readonly RelayCommand _removeMe;
private string _original;
private string _encoded;
private readonly IStructureManager _manager;
public string Original
{
get
{
return _original;
}
set
{
_original = value;
Encoded = ReverseString(_original);
NotifyPropertyChanged();
}
}
public string Encoded
{
get
{
return _encoded;
}
set
{
_encoded = value;
NotifyPropertyChanged();
}
}
public ICommand RemoveMeCommand
{
get
{
return _removeMe;
}
}
public Model(IStructureManager manager, string original)
{
Original = original;
_manager = manager;
_removeMe = new RelayCommand(param => RemoveMe(), param => CanRemoveMe);
}
private void RemoveMe()
{
_manager.RemoveItem(this);
}
private bool CanRemoveMe
{
get
{
//Logic to enable/disable button
return true;
}
}
private string ReverseString(string s)
{
char[] arr = s.ToCharArray();
Array.Reverse(arr);
return new string(arr);
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
RelayCommand implementation
From here on out all you have to do is change the attributes of your controls to whatever you're happy with and call it good. The example might be ugly, but I'm leaving it as an exercise for you to figure out other properties/attributes of WPF controls.
In WPF with MVVM it's easy to fire some code when the user changes the tab.
<TabControl Margin="0 5 5 5" Background="#66F9F9F9" SelectedIndex="{Binding TabIndex}">
And then in the ViewModel:
private int _tabIndex;
public int TabIndex
{
get { return _tabIndex; }
set
{
if(_tabIndex != value)
{
_tabIndex = value;
OnPropertyChanged("TabIndex");
if(value == 1)
{
//do something
}
}
}
}
But I'm vaguely uncomfortable with this. What if another developer happens along later and adds another tab in the "1" position. If this is application-critical code (which it is), things will break spectacularly.
Danger can be minimized with unit tests, of course. But it made me wonder: is this seen as bad practice? And is there a way of doing this that allows you to refer to the Tab with a string, rather than an int? I tried noodling with binding to the SelectedValue property, but nothing seemed to happen when the tabs were changed.
You could make a behavior for TabItem, listening for changes to the IsSelected dependency property, and raises a Command when the tab is selected. This can be extended to any number of tabs, each which invokes different commands in the viewmodel. You could also supply a command parameter for any optional context:
class TabSelectedBehavior : Behavior<TabItem>
{
public static readonly DependencyProperty SelectedCommandProperty = DependencyProperty.Register("SelectedCommand", typeof(ICommand), typeof(TabSelectedBehavior));
public ICommand SelectedCommand
{
get { return (ICommand)GetValue(SelectedCommandProperty); }
set { SetValue(SelectedCommandProperty, value); }
}
private EventHandler _selectedHandler;
protected override void OnAttached()
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TabItem.IsSelectedProperty, typeof(TabItem));
if (dpd != null)
{
_selectedHandler = new EventHandler(AssociatedObject_SelectedChanged);
dpd.AddValueChanged(AssociatedObject, _selectedHandler);
}
base.OnAttached();
}
protected override void OnDetaching()
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TabItem.IsSelectedProperty, typeof(TabItem));
if (dpd != null && _selectedHandler != null)
{
dpd.RemoveValueChanged(AssociatedObject, _selectedHandler);
}
base.OnDetaching();
}
void AssociatedObject_SelectedChanged(object sender, EventArgs e)
{
if (AssociatedObject.IsSelected)
{
if (SelectedCommand != null)
{
SelectedCommand.Execute(null);
}
}
}
}
XAML
<TabControl>
<TabItem Header="TabItem1">
<i:Interaction.Behaviors>
<local:TabSelectedBehavior SelectedCommand="{Binding TabSelectedCommand}"/>
</i:Interaction.Behaviors>
</TabItem>
<TabItem Header="TabItem2">
</TabItem>
</TabControl>
In a similar fashion you could also make a behavior for the TabControl, which turns the SelectionChanged event into a command, and pass the Tag object of the selected TabItem as command parameter.
As with all collection controls, the best way to maintain the selected item is to use the SelectedItem property. If you data bind a property of the relevant data type to the TabControl.SelectedItem property, then you'll still be able to tell which tab is selected and select a different one from the view model.
The only problem with this method is that you'll also need to use the TabControl.ItemsSource property to set up the TabItems:
<TabControl ItemsSource="{Binding YourDataItems}" SelectedItem="{Binding YourItem}" />
If you want to try this, then you should know that defining the TabItems can be a little bit confusing. Please refer to the answer from the How to bind items of a TabControl to an observable collection in wpf? question for help with that.
Objective: Set the visibility of a control based on the selected value of a ComboBox
Issue: The property that is being used to check the visibility is in the VM, however I don't know how to use it as DataContext is already defined to another object, i.e would I need to bind 2 datacontexts?!
Details:
I have a CustomControl that I load in my view associating to it a DataContext (a List of objects that is displayed as a grid:
<GUI:Counterparties_UserInputs x:Name="UserInputs" DockPanel.Dock="Right" DataContext="{Binding Source={StaticResource counterpartiesDataView}}"/>
In that user control I have some StackPanel which visibility should be triggered based on the selection of a ComboBox:
<ComboBox ItemsSource="{Binding Source={StaticResource CounterpartyTypes}}" SelectedValue="{Binding SelectedCounterpartyType}"/>
<StackPanel Visibility="{Binding Path=SelectedCounterpartyType,Converter={StaticResource SelectedValueToVisible}}"/>
The issue I have is that the code behind is never hit as I don't find how to associate an "extra" DataContext to the view.
Here is my code behind:
public partial class Counterparties_UserInputs : UserControl
{
...
public Counterparties_UserInputs()
{
// this.DataContext = _cptyUserInputsVM;
_cptyUserInputsVM = new Counterparties_UserInputs_VM();
InitializeComponent();
}
}
And the ViewModel where the Property "SelectedCounterpartyType" is never hit:
public class Counterparties_UserInputs_VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _selectedCounterpartyType;
public string SelectedCounterpartyType
{
get
{
return _selectedCounterpartyType;
}
set
{
_selectedCounterpartyType = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedCounterpartyType"));
}
}
}
}
I've seen that answer already but it's not exactly what I am doing... So would really appreciate your help! Thank you!