My name is Andrea this is my first post ever.
Frequently you have helped me as a simple reader, now I'm writing because I wanted to direct support.
I have to create and a tab control and with a button "Add Tab" I have to add a new tab with the same content.
Up to this everything is fine.
Within Tab I have a textedit and a combobox.
My problems are two:
1 How do I load the contents of the combobox for each tab I add?
2 Every time I write the text of and a edit tab override also edit the text of the other tab.
Here the code:
Data Template in Xaml:
<DataTemplate x:Key="tabItemContent">
<dxlc:LayoutGroup Orientation="Vertical" Header="Target Description" IsCollapsible="True">
<!--Name-->
<dxlc:LayoutItem>
<dxlc:LayoutGroup Orientation="Horizontal" ItemSpace="4" >
<dxlc:LayoutItem Label="Name" Margin="10">
<dxe:TextEdit x:Name="TextEdit_NameTarget"/>
</dxlc:LayoutItem>
</dxlc:LayoutGroup>
</dxlc:LayoutItem>
<!--Nation e Label-->
<dxlc:LayoutItem>
<dxlc:LayoutGroup Orientation="Horizontal" ItemSpace="12" >
<dxlc:LayoutItem Label="Nation" Margin="10">
<ComboBox x:Name="ComboBox_TargetNazione" />
</dxlc:LayoutItem>
</dxlc:LayoutGroup>
</dxlc:LayoutItem>
</dxlc:LayoutGroup>
</DataTemplate>
C#:
private void Button_Click_Add(object sender, RoutedEventArgs e)
{
DataTemplate tabItemDataTemplate = this.TryFindResource("tabItemContent") as DataTemplate;
DXTabItem tabItem = new DXTabItem();
tabItem.Header = "New Tab";
tabItem.ContentTemplate = tabItemDataTemplate;
tabControl_Targets.Items.Add(tabItem);
}
Here's where to load the list into the combobox:
private void LoadComboBoxNation()
{
ComboBox_TargetNazione.ItemsSource =
ManagementTriple.Istance().get_Nation_byTipologyAndContext(ComboBox_TypologyScenario.SelectedItem.ToString(),
ComboBox_ContextScenario.SelectedItem.ToString());
controlloselecteditem(ComboBox_SourceNazione.SelectedItem.ToString());
controlloselecteditem(ComboBox_TargetNazione.SelectedItem.ToString());
}
Thank you all for the help that you can give me.
DataTemplates require a simple but fundamental requirement to work properly: you should use the ViewModel-First approach.
Ideally, your tab control should have a Binding to some ViewModel. Then, if you want another tab to appear, you should use your button click to call a Command in your ViewModel, then the ViewModel would add another item to your TabControl ItemsSource property (which would be some collection), and the new item would be displayed "automagically" with its respective DataTemplate.
The idea of WPF is to replace all this imperative code in the View (like the one you posted) with a more indirect one, where your only worry is to manipulate things in the ViewModel, and the "Dumb View" just follows.
Hope this helps, but don't hesitate to ask for additional details in the comments.
Related
I have a Pivot control that uses an ItemsSource to bind to a list of ViewModel instances. I assign a custom ItemTemplateSelector to map between ViewModel types and DataTemplate definitions. This all works fine and the correct display is created for each ViewModel based on the associated DataTemplate. Something like this...
<Pivot ItemsSource="{x:Bind ViewModels}"
ItemTemplateSelector="{StaticResource ViewModelSelector}"
SelectedItem="{x:Bind SelectedViewModel, Mode=TwoWay}"/>
The problem is that I want to automatically set focus to a control within each page when that page is first shown. They are typically data entry forms and so the user currently has to select the first control to start entering data. It would be better if first showing a page automatically then set focus to a control on that page.
Any ideas?
You could bind a method to the TextBox when it's loaded.
For example:
<Pivot.ItemTemplate>
<DataTemplate x:DataType="local:Test">
<TextBox Text="{x:Bind Content}" Height="50" Loaded="{x:Bind TextBox_Loaded}">
</TextBox>
</DataTemplate>
</Pivot.ItemTemplate>
public void TextBox_Loaded(object sender, RoutedEventArgs e)
{
TextBox textBox = sender as TextBox;
if (textBox != null)
{
textBox.Focus(FocusState.Programmatic);
}
}
So, I am fairly new to MVVM and have backed myself into an interesting corner where I am not sure how to make things work with either a behavior or a command. I have a user control that contains a listbox of items which need to implement various behaviors such as deleting or removing the given item. Like so:
<UserControl> // DataContext is a viewmodel
// Borders, grids, various nesting controls...
<ListBox x:Name="ListBox_Items" ItemSource="{Binding ItemsList}">
<ListBox.ItemTemplate>
<DataTemplate> // From here on the individual item has its own data context of type Item in ItemsList
<StackPanel Orientation="Horizontal">
<TextBox Name="EditItemStuffOnLoseFocus" Text="{Binding ItemStuff}"/>
<Button Name="DeleteItemStuff"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
The example has been simplified, but the basic idea is that the textbox should edit its associated listbox item when it loses focus and the button should delete the associated listbox item when pressed. At first I implemented commands for this and had both working, until I realized that I had forgotten the standard "Are you sure?" message. I added this in the command, but since it has no concept of the actual objects, I can't think of how to tell it where to put the dialog window. The command accepts a view model (_ViewModel) on creation and accepts the Item model (textbox/button's DataContext) as a parameter. With the basic message box dialog, the Execute() method looked something like this (simplified):
public void Execute(object parameter)
{
if (MessageBox.Show("Really delete the item?", "Delete Item", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
{
ItemService service = new ItemService();
service.RemoveItem(((Item)parameter).ItemID);
if (_ViewModel.ReloadItemListCommand.CanExecute(_ViewModel.ItemInfo))
_ViewModel.ReloadItemListCommand(_ViewModel.ItemInfo);
}
}
Of course, this message box is not centered on the application, which is small but annoying. A coworker suggested that I replace the Command with a Behavior so that I would have an associated object to use for centering the message box. The problem is, I haven't been able to find any information on passing parameters to a behavior, or how to trace back multiple levels from an associated object to its parents so that I can get the view model for the reloading step as well as the individual item's model (the associated object's DataContext).
In summary, is there a way to either center the MessageBox on the application within the command while remaining MVVM-friendly, OR to pass parameters / retrieve a specific parent object or its resources using a behavior?
____________ UPDATE ____________
The answer below works great, but I went another route so that I could use DataContext variables in my MessageBox. I managed to preserve access to the DataContext of the calling control and the view model by adding the view model to the control's tag:
<UserControl> // DataContext is a viewmodel
// Borders, grids, various nesting controls...
<ListBox x:Name="ListBox_Items" ItemSource="{Binding ItemsList}">
<ListBox.ItemTemplate>
<DataTemplate> // From here on the individual item has its own data context of type Item in ItemsList
<StackPanel Orientation="Horizontal">
<TextBox Name="EditItemStuffOnLoseFocus" Text="{Binding ItemStuff}" Tag={Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=DataContext}"/>
<Button Name="DeleteItemStuff" Tag={Binding RelativeSource={RelativeSource AncestorType=ListBox}, Path=DataContext}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
I'm not entirely certain this is the best way to be using Tag, but it does get all the information I need into the behavior while allowing me to center the MessageBox. The behavior is similar to the command except for a few added lines to extract the model and view model. Keeping with the initial shortened example, it looks something like this:
ExampleViewModel viewModel = (ExampleViewModel)AssociatedObject.Tag;
Item parameter = (Item)AssociatedObject.DataContext;
if (MessageBox.Show(Window.GetWindow(AssociatedObject), "Really delete the item?", "Delete Item", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
{
ItemService service = new ItemService();
service.RemoveItem(((Item)parameter).ItemID);
if (viewModel.ReloadItemListCommand.CanExecute(viewModel.ItemInfo))
viewModel.ReloadItemListCommand(viewModel.ItemInfo);
}
}
Thank you all for the help.
To center a message box on a window will require you to either implement your own Window and do a ShowDialog where you can specify the location. Or you can inherit from the Forms control done in this CodeProject Solultion.
However for the first part of your problem It would most likely be easier to implement a click handler on the button and bind the delete to your user control as a dependency property. This would allow you to have access to the sender and keep the UI compeltely inside the control.
xaml
<ListBox ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}" />
<Button Click="Button_Click" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code Behind
public ICommand DeleteItem
{
get { return (ICommand)GetValue(DeleteItemProperty); }
set { SetValue(DeleteItemProperty, value); }
}
public static readonly DependencyProperty DeleteItemProperty = DependencyProperty.Register("DeleteItem", typeof(ICommand), typeof(control), new PropertyMetadata(null));
private void Button_Click(object sender, RoutedEventArgs e)
{
if (DeleteItem != null)
{
var result = System.Windows.MessageBox.Show("WOULD YOU LIKE TO DELETE?", "Delete", MessageBoxButton.YesNo, MessageBoxImage.Warning);
if (result == MessageBoxResult.Yes)
{
var fe = sender as FrameworkElement;
if (DeleteItem.CanExecute(fe.DataContext))
{
DeleteItem.Execute(fe.DataContext);
}
}
}
Just have your delete command bind from the outside and do the logic for your message box in the click event.
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.
All,
I have searched extensively for the solution here but I have a feeling my problem stems from a basic lack of knowledge about WPF. I am new to it and have thus far hacked and Googled my way through as best I can.
Basically, I have a Ribbon dynamically interacting with a TabControl. The Ribbon tabs select a category of items, the MenuItems in the RibbonGroups then choose an item within the category. Upon clicking an item, the tabs on the TabControl need to dynamically change. Whether that is just the Headers, the tabs themselves, or the entire TabControl is fine with me. Thus far, upon clicking a MenuItem on the inside one of the RibbonGroups, I attempt to just set the Header text equal to "blah" for each tab on the TabControl. Then the Header object throws a null pointer. This is what happens whether I set the Header, the Tabs, or the TabControl itself.
Why?!?!?!?
...and how in the world do I fix it???
Thanks!
WPF is designed with data/UI separation in mind. One of the reasons you're having trouble finding a solution is because what you're trying to do is a no-no; instead of programmatically changing the UI's header text, you should be changing the underlying data instead, and allowing the WPF plumbing to update how the data is displayed.
A WPF tab control can literally contain any type of object; you could fill it with integers or strings or FooBars or whatever. There's no guarantee that any of these objects will define a Header property, and it's up to the developer to configure data bindings or templates which instruct the TabControl just how a FooBar or a whatever should be displayed.
In an ideal WPF application which adheres to the MVVM design pattern, you might have your TabControl bound to a collection of view models which each define a HeaderText property. Your view models would implement the INotifyPropertyChanged interface so that when the HeaderText property was changed on the view model then the UI would get updated.
Having said all that, if you've got an existing application it may be unrealistic to rewrite it from scratch using a different design pattern, and MVVM is not easily added on to an existing code base. If you're working with simple Designer generated UI without using any data binding, then the following code does what you ask. Sometimes.
foreach(TabItem item in tabControl.Items)
item.Header = "blah";
... but as I said before, there's no guarantee that a WPF TabControl's Items collection will contain items of type TabItem, so this code is not safe.
While RogerN's answer is probably a better answer, here is a code sample that changes the text that appears on a tab:
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl Name="MyTabControl">
<TabItem Header="Tab One">
<TextBlock Text="This is tab #1." />
</TabItem>
<TabItem Header="Tab Two">
<TextBlock Text="This is tab #2." />
</TabItem>
<TabItem Header="Tab Three">
<TextBlock Text="This is tab #3." />
</TabItem>
</TabControl>
<Button Grid.Row="1" Content="Change Tab" Name="ChangeButton" Click="ChangeButton_Click" />
</Grid>
Code behind:
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private void ChangeButton_Click(object sender, RoutedEventArgs e) {
((TabItem)MyTabControl.Items[0]).Header = "Changed!";
}
}
Try binding it to a list in the code like so:
private List<TabItem> TabItems = new List<TabItem>()
{
"Item1",
"Item2",
"Item3"
};
tabcontrol1.ItemSource = TabItems;
Then rebind it any time you want to change the items in the tabcontrol. This way you can dynamically change names and add more tab items. In doing this you'll have to programmatically add controls using the TabItem.Content property.
What would be the best way to build a data-navigation like in access-forms in XAML/C#?
Should I build a user control (or even custom control) that I just bind to my collection in which I can put other controls? (hence this question: C# User Control that can contain other Controls (when using it) )
Or can I build something by deriving from then ItemsControl somehow? how?
Or would this be done completely different today (like "this style of navigation is so last year!")?
I'm relatively new to C# and all (not programming as such, but with more like "housewife-language" Access-VBA) also I'm no native english speaker. So pls be gentle =)
You can create user control and place a bunch of buttons (First, Prev, Next, Last, etc..) in it and place it on the main window. Secondly, you can bind your data navigation user control to a CollectionViewSource which will help you to navigate among your data.
Your main window:
<Window.Resources>
<CollectionViewSource x:Key="items" Source="{Binding}" />
</Window.Resources>
<Grid>
<WpfApplication1:DataNavigation DataContext="{Binding Source={StaticResource items}}" />
<StackPanel>
<TextBox Text="{Binding Source={StaticResource items},Path=Name}" />
</StackPanel>
</Grid>
Your Data Navigation User Control:
<StackPanel>
<Button x:Name="Prev" Click="Prev_Click"><</Button>
<Button x:Name="Next" Click="Next_Click">></Button>
<!-- and so on -->
</StackPanel>
And your click handlers goes like this:
private void Prev_Click(object sender, RoutedEventArgs e)
{
ICollectionView view = CollectionViewSource.GetDefaultView(DataContext);
if (view != null)
{
view.MoveCurrentToPrevious();
}
}
I hope this helps.
Sounds like you're after a DataGrid control. Microsoft is releasing a WPF DataGrid as part of a WPF Toolkit which you can download here: http://wpf.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=25047.