I'm creating something that has buttons, to show different info. I don't want to create a lot of buttons, but I don't know how to have the buttons show different information.
<Button x:Name="btn1"
Content=""
Click="btn1_Click"/>
<Button x:Name="btn2"
Content=""
Click="btn2_Click"/>
<Button x:Name="btn3"
Content=""
Click="btn3_Click"/>
<Button x:Name="btn4"
Content=""
Click="btn4_Click"/>
<Button x:Name="btn5"
Content=""
Visibility="Hidden"
Click="btn5_Click"/>
<Button x:Name="btn6"
Content=""
Visibility="Hidden"
Click="btn6_Click"/>
<Button x:Name="btnBack"
Content="Return to Main Menu"
Visibility="Hidden"
Click="btnBack_Click"/>
if you click btn1 from the main menu and click it again it will show info, but after clicking btn2 from the main menu and clicking btn1, it will show different info. I don't know how I could do that, and nothing i searched up has helped me so far.
Anything with a bunch of generic controls created by hand has a smell to it in WPF and your's is no different. This may sound bad, but in reality is something extremely good when it comes to WPF. Why? Because this means you get to learn how dynamic WPF is and you get to hopefully learn how to overcome the smell (at least for this situation).
Disclaimer: The pieces and ideas are here, but making a full working solution is not needed and may actually hinder the learning process.
You obviously have some dynamic buttons and you want to control visibility, functionality, and display. If we attempt to do these with buttons already created as you have, it gets nasty and dirty very quickly. So then what do we do? Well, let's create a small class to hold some of our button information.
public class MyButton : INotifyPropertyChanged
{
//Assume all properties did change notifications
public string Text { get; set; }
public ICommand Command { get; set; }
public Visibility Visibility { get; set; }
}
Now let's just assume you have either a ObservableCollection of these buttons in either a ViewModel or the Window itself.
You may be thinking, "well, that's great and all, but I am still having to bind all of this to the buttons I have." You would be wrong!
WPF has many containers. One of them being an ItemsControl. With that we can take our collection of MyButton and have it do all the leg work for us. Can you taste the cleanliness? Tasty isn't it?
<ItemsControl Items="{Binding YourCollectionOfMyButton}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Text}"
Command="{Binding Command}"
Visibility="{Binding Visibility}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now there's some UI work to do to make it look pretty, but there you go. Now it doesn't matter what buttons you have, you don't need to do any work other than updating that collection.
How you want to deal with managing what buttons go in that collection is up to you, but do know that you are no longer jumping through the hoops to manage hardcoded X number of buttons. You can clear the collection, add 1 MyButton and be done with it. Need to add 30? You can!
You can do something like this
switch (btn.Text)
{
case "first":
btn.Text="second";
break;
case "second":
btn.Text="third";
break;
}
Maybe you can use an enum to your advantage.
An example would be:
public enum Action
{
Button1_Action1,
Button1_Action2
}
// In your form/window
private Action _currentAction = Action.Button1_Action1;
// After you click btn2
_currentAction = Action.Button1_Action2;
// After you click btn1
switch (_currentAction)
{
case Action.Button1_Action1:
// Do something
break;
case Action.Button1_Action2:
// Do something else
break;
}
Hope this gives you an idea :)
Related
I'm developing a WPF app using MVVM where I show error messages/special dialogs via a lightbox styled pop-up. These sub-Views are User Controls displayed by a ContentControl in the main View.
Up until now, each sub-View has been hardcoded to perform a single function (say, display error or ask the user to backup first). But as the app progresses, I'm seeing the the same design pattern from most of these controls:
Icon image in top left corner
Heading text next to icon
Message text in the middle
2 buttons in the bottom right corner
With MVVM I should be able to abstract this pattern and reuse this control for displaying errors, asking the user to back up, anything really, just by binding. I should even be able to bind the names of the buttons or even hide 1 if it isn't used...stuff like that.
But should I? Is there a performance benefit/hit from doing it like this? Seems like this would fall under DRY when there's 8 sub-views all with the same grid pattern.
Dry is not about performance.
It's about saving you time writing code and in the maintenance phase.
Whilst it would be more elegant to make one generic re-usable window this probably comes at some sort of cost.
Does the work cost you more than the benefit you get? The decision whether to rationalise into one probably-more-complicated view or not should be based on a sort of cost benefit analysis.
Factors you should consider:
How long does it take to make each view?
How complicated is the functionality in each?
How much effort is necessary to make a generic?
How many exceptional cases are there and how much would they complicate making this generic?
Would making this generic obscure functionality and to what extent is it going to make maintenance more expensive?
How likely is it you'll have to change the look of these things?
If you're highly unlikely to change the look, there are a few edge cases make a generic view complicated and injecting your functionality has complications then just copying and pasting markup into each view makes some sense.
Edit:
Remember that styling is re-usable.
Here's a concrete bit of markup to consider.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<Path Data="{Binding IconGeometry}"
Stretch="Fill"
Fill="Black"
Height="28"
Width="28"/>
<TextBlock Text="{Binding Heading}"/>
</StackPanel>
<TextBlock Grid.Row="1"
Text="{Binding Message}"/>
<ItemsControl
Grid.Row="2"
ItemsSource="{Binding NamedCommandCollection}"
HorizontalAlignment="Right">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding ButtonText}" Command="{Binding ButtonCommand}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
You present a viewmodel to this.
This viewmodel implements inotifypropertychanged and provides a string property for the heading, message etc.
The less-obvious things here are a path with a geometry rather than image. This depends on what your iconography will look like exactly but the simple one colour shape is very common now.
You can define geometries in a resource dictionary, grab the appropriate one out of there and supply it as a property. Merged resource dictionaries go in application.current.resources which is pretty much an in memory dictionary of objects keyed by a string of your x:key.
The buttons are produced by an itemscontrol which templates out it's items into a horizontal line of buttons.
Build a viewmodel representing a button.
string property for name and a relaycommand or delegatecommand for the ButtonCommand.
Let's call that a ButtonVM.
Add a ButtonVM to an observablecollection property NamedCommandCollection and you get a button appears. Add one, two, three. However many you like.
You could make the ButtonVM just take a relaycommand you build and supply or have one itself and you inject an action. You can capture variables as you build an action dynamically.
Command also has canexecute. You can use that to refine when a button can be clicked or not. EG I have a property IsBusy in a base viewmodel which I use to flag whether any command is "running" to obviate that very fast double click breaking everything.
Here it is:
public class BaseViewModel : INotifyPropertyChanged
{
private bool isBusy;
[IgnoreDataMember]
public bool IsBusy
{
get => isBusy;
set => ToVal(ref isBusy, value, nameof(IsBusy));
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void ToObj<T>(ref T backer, T value, [CallerMemberName] string propertyName = null)
{
backer = value;
this.RaisePropertyChanged(propertyName);
}
public void ToVal<T>(ref T backer, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(backer, value))
{
return;
}
backer = value;
this.RaisePropertyChanged(propertyName);
return;
}
}
Icommand has a canexecute bool and will disable a control a command is bound to if it's false. However, this relies on commandmanager deciding to requery canexecute and disable that control. There are circumstances when this won't happen fast enough. Hence it's best to use the bool to guard the code in a command.
A fairly random example out some real code:
private RelayCommand newMapCommand;
public RelayCommand NewMapCommand
{
get
{
return newMapCommand
?? (newMapCommand = new RelayCommand(
() =>
{
if (IsBusy)
{
return;
}
ResetMap();
IsBusy = false;
},
( ) => !IsBusy
));
}
}
Relaycommand is in mvvmlight. Since I work in net core nowadays and there's a dependency in commandwpf on net old, I grabbed the source for the bits I want of mvvmlight. I retain the namespaces since Laurent will probably eventually address this or net 5 may obviate the issue.
A usercontrol can itself contain a usercontrol. If you wanted flexibility then it could have a contentcontrol and template out what is bound to it's content.
This is used for viewmodel first, a common way to switch out content for navigation etc. I wrote an example to explain the evils of pages :^)
https://social.technet.microsoft.com/wiki/contents/articles/52485.wpf-tips-and-tricks-using-contentcontrol-instead-of-frame-and-page-for-navigation.aspx
I'm inexperienced with both WPF and MVVM so i'm most likeley missing something but when I click my button the command isn't firing. I also have some menu controls on my page that i've setup the exact same way and when I click those, their commands work as expected.
I've tried attaching a click event handler to make sure the button is definitely being clicked which it is. I've also tried attaching a different command that works on my menu control which didn't work on the button.
<Button Grid.Row="1" Content="Add Note"
Command="{Binding InsertNoteCommand}"/>
public ICommand InsertNoteCommand { get; }
public MainViewModel()
{
InsertNoteCommand = new RelayCommand(InsertNote);
}
private void InsertNote()
{
Console.WriteLine("Note Inserted!");
}
I should also mention that i'm using MVVM Light
The debugging information is very useful to know but in the end I solved the problem by pointing the binding to the data context.
<Button x:Name="AddNewNoteBtn" Grid.Row="1" Content="Add Note"
Command="{Binding Path=DataContext.InsertNoteCommand, ElementName=_window}"/>
If anybody has comments on how I can improve this I would really appreciate it. Thanks!
I'm trying to find the best solution for a TabControl that both support a close button on each TabItem, and always show a "new tab button" as the last tab.
I've found some half working solutions, but i think that was for MVVM, that I'm not using. Enough to try to understand WPF =)
This is the best solution I've found so far:
http://www.codeproject.com/Articles/493538/Add-Remove-Tabs-Dynamically-in-WPF
A solution that i actually understand. But the problem is that it is using the ItemsSource, and i don't want that. I want to bind the ItemsSource to my own collection without having to have special things in that collection to handle the new tab button.
I've been search for days now but cant find a good solution.
And I'm really new to WPF, otherwise i could probably have adapted the half done solutions I've found, or make them complete. But unfortunately that is way out of my league for now.
Any help appreciated.
I have an open source library which supports MVVM and allows extra content, such as a button to be added into the tab strip. It is sports Chrome style tabs which can tear off.
http://dragablz.net
This is bit of a dirty way to achieve the Add (+) button placed next to the last TabItem without much work. You already know how to place a Delete button next to the TabItem caption so I've not included that logic here.
Basically the logic in this solution is
To bind ItemsSource property to your own collection as well as
the Add TabItem using a CompositeCollection.
Disable selection of
the Add(+) TabItem and instead perform an action to load a new tab when it
is clicked/selected.
XAML bit
<TextBlock x:Name="HiddenItemWithDataContext" Visibility="Collapsed" />
<TabControl x:Name="Tab1" SelectionChanged="Tab1_SelectionChanged" >
<TabControl.ItemsSource>
<CompositeCollection>
<CollectionContainer Collection="{Binding DataContext.MyList, Source={x:Reference HiddenItemWithDataContext}}" />
<TabItem Height="0" Width="0" />
<TabItem Header="+" x:Name="AddTabButton"/>
</CompositeCollection>
</TabControl.ItemsSource>
</TabControl>
The code behind
private void Tab1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Contains(AddTabButton))
{
//Logic for adding a new item to the bound collection goes here.
string newItem = "Item " + (MyList.Count + 1);
MyList.Add(newItem);
e.Handled = true;
Dispatcher.BeginInvoke(new Action(() => Tab1.SelectedItem = newItem));
}
}
You could make a converter which appends the Add tab. This way the collection of tabs in you viewmodel will only contain the real tabs.
The problem is then how to know when the Add tab is selected. You could make a TabItem behavior which executes a command when the tab is selected. Incidentally I recommended this for another question just recently, so you can take the code from there: TabItem selected behavior
While I don't actually have the coded solution, I can give some insight on what is most likely the appropriate way to handle this in a WPF/MVVM pattern.
Firstly, if we break down the request it is as follows:
You have a sequence of elements that you want to display.
You want the user to be able to remove an individual element from the sequence.
You want the user to be able to add a new element to the sequence.
Additionally, since you are attempting to use a TabControl, you are also looking to get the behavior that a Selector control provides (element selection), as well as an area to display the element (content) which is selected.
So, if we stick to these behaviors you'll be fine, since the user interface controls can be customized in terms of look and feel.
Of course, the best control for this is the TabControl, which are you already trying to use. If we use this control, it satisfies the first item.
<TabControl ItemsSource="{Binding Path=Customers}" />
Afterwards, you can customize each element, in your case you want to add a Button to each element which will execute a command to remove that element from the sequence. This will satisfy the second item.
<TabControl ...>
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=CustomerId}" />
<Button Command="{Binding Path=RemoveItemCommand, Mode=OneTime,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type TabControl}}"
CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
<TabControl.ItemTemplate>
</TabControl>
The last part is a bit more difficult, and will require you to actually have to create a custom control that inherits from the TabControl class, add an ICommand DependencyProperty, and customize the control template so that it not only displays the TabPanel, but right next to it also displays a Button which handles the DependencyProperty you just created (the look and feel of the button will have to be customized as well). Doing all of this will allow you to display your own version of a TabControl which has a faux TabItem, which of course is your "Add" button. This is far far far easier said than done, and I wish you luck. Just remember that the TabPanel wraps onto multiple rows and can go both horizontally or vertically. Basically, this last part is not easy at all.
Lets say I have a xaml file, a window, why not. in this xaml I have a grid with multiple labels, textBoxs, comboBoxs, lists... You see the patern. At a certain point (where X == true for say) I want to be able to catch a click inside the grid and everything in it.
I want to be still able to do what this click was going to do so a full-filled Rect over the grid is not the answer I'm looking for. The action of the click would be to put X back to false. nothing much.
Is there an easy way to manage a click on a grid and everything inside it?
Thanks in advance
You just need to use an event that is common to all of the controls. Probably the best one for this scenario is the UIElement.PreviewMouseDown event. Try this:
<StackPanel UIElement.PreviewMouseDown="StackPanel_PreviewMouseDown">
<Label Content="I'm a Label" />
<Button Content="I'm a Button" />
<CheckBox Content="I'm a CheckBox" />
</StackPanel>
You need to use one of the Preview... events so that you can catch it before the Buttons consume it... the UIElement.MouseDown event wouldn't work with Buttons for that very reason. However, you can use the othwer Preview... methods, like the UIElement.PreviewLeftMouseButtonDown event, for example.
Can you give your sample code?
From my understanding,you can use this,it will capture all your click inside grid.
.xaml
<Grid MouseDown="Grid_MouseDown">
<Label MouseDown="Grid_MouseDown" />
<Button MouseDown="Grid_MouseDown"/>
<Button MouseDown="Grid_MouseDown"/>
</Grid>
.xaml.cs
private Grid_MouseDown(object sender,MouseButtonEventArgs e)
{
if(X==true)
{
//doSomething
}
else
{
//do SomethingElse
}
}
edit: How about this?
I am pretty new to WPF, and in order to get some knowledge I decided to make a very simple UML modeling program, that basically offers the possibility to put some classes onto a canvas, connect them and move them around.
Now to the question:
I have been thinking about letting the classes I put on the canvas being a userControl I design. In my mind it would be something like a Grid, with some textboxes to represent properties, attributes and so on. The actual question is then, is my idea possible, or should I go with something completely different? My concern right now is how to implement the grid such that it can expand (add a row) under the right heading (Attribute/property..) when I want it to, and not be expanded to a maximum from the beginning.
I hope you can understand my question, and give me an idea to whether I should continue to implement it how I thought about, or do it using some other method.
You may wish to consider a ListView control, perhaps with an Expander, something like this:
<Canvas>
<Expander Header="Stuff"
MaxHeight="900"
Canvas.Left="202"
Canvas.Top="110">
<ListView Name="MyListView">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Add new thing"
Click="MenuItem_Click" />
</ContextMenu>
</ListView.ContextMenu>
<ListViewItem>
<StackPanel Orientation="Horizontal">
<Label>Name</Label>
<TextBox Text="Value" />
</StackPanel>
</ListViewItem>
<ListViewItem>Item two</ListViewItem>
<ListViewItem>Item three</ListViewItem>
</ListView>
</Expander>
</Canvas>
This will size as needed up to the max given. The list view items could contain any sort of content (not just text) as you can see above. You will want to learn a bit about Style and Control templates. WPF has IMHO a rather steep learning curve but there are a lot of learning resources on the web. Good luck.
In response to your comment, I'm adding additional information.
Anything you can do in XAML you can do in code behind (mostly XAML just calls framework objects). In this case I've added a context menu to the ListView control. This menu contains one item "Add new thing". There is a Click event for this item which is bound to the MenuItem_Click method in the code behind. I then added this method to the code:
void MenuItem_Click(object sender, RoutedEventArgs e) {
var lvi = new ListViewItem();
lvi.Content = String.Format("New thing {0}", DateTime.Now);
MyListView.Items.Add(lvi);
}
Now if you right click in the ListView you will see the "Add new thing" menu selection, left clicking it adds a new ListViewItem into the ListView (programmatically).