I want to modify the original ListBox control that the each item to have a CheckBox, Labels and a Button control inside.
Is there any optimal method to make that? without making Custom Control from the very beginning?
Making custom control that inherits ListBox could be not a bad idea, but don't know how...
Thank you!
I tried WPF but it was too difficult at this time. Actually, designing the control via XAML was easy, but managing the list items(add/delete with texts, get event from the button in each item) wasn't.
Since the question is tagged [WPF] I'm going to provide a WPF answer:
The first thing any developer who faces WPF immediately tries to do is to use it as if it were winforms. This is a big mistake.
If you're working with WPF, you really need to leave behind the traditional aproach used in archaic technologies such as winforms, and understand and embrace The WPF Mentality.
in WPF, you don't "add/delete with texts, get event from the button in each item" or any of that, simply because UI is not Data.
Instead, you define a simple Data Model:
public class MyData
{
public string MyText1 {get;set;}
public string MyText2 {get;set;}
}
and then declaratively define Data Bindings in the UI to "show" this data to the UI as opposed to "reading" or "writing" data to/from the UI:
<Window x:Class="WpfApplication14.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication14"
Title="MainWindow" Height="350" Width="525">
<ListBox ItemsSource="{Binding}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Margin="5" Background="LightCyan" BorderBrush="LightSkyBlue" BorderThickness="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Text 1:" HorizontalAlignment="Right"/>
<Label Grid.Row="1" Grid.Column="0" Content="Text 2:" HorizontalAlignment="Right"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding MyText1}"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding MyText2}"/>
<Button Grid.Row="2" Grid.ColumnSpan="2" Content="Button" HorizontalAlignment="Center"/>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
And finally, you define the DataContext of the Window or View to a relevant instance or collection of such data:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = Enumerable.Range(0,10)
.Select(x => new MyData()
{
MyText1 = "Text1 - " + x.ToString(),
MyText2 = "Text2 - " + x.ToString()
});
}
}
All this results in:
See? really simple and beautiful.
Forget winforms, WPF Rocks. Just copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.
The best way to do this, short of using WPF, is to create a custom UserControl which represents each item that will go in the list. Then, add a FlowLayoutPanel to your form. Set the panel's AutoScroll property to True. Then set its FlowDirection property to TopToBottom. Then, dynamically create one of your custom controls for each item that you need to add to the list and call the panel's Controls.Add method to add them to the list.
Related
This is a simplified example. I have a usercontrol that contains a "browse to folder" functionality, using a textbox and a button. Clicking the button would open up the browse-dialog, and would essentially fill in the textbox.
<UserControl x:Class="MyUserControl"
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="300" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<!-- Folder -->
<TextBlock>Path</TextBlock>
<DockPanel LastChildFill="True" Grid.Column="1">
<Button DockPanel.Dock="Right" cal:Message.Attach="[Event Click] = [Action BrowseHotFolder()]" Content="..." HorizontalAlignment="Left" Width="25" Height="25" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Margin="0,0,5,0"/>
<TextBox Text="{Binding HotFolderPath, UpdateSourceTrigger=PropertyChanged, ValidatesOnNotifyDataErrors=True}" />
</DockPanel>
</Grid>
I have a listbox that contains a number of objects. The selected object will be fed into this usercontrol as datacontext.
<Window>
...
<Listbox ItemsSource="{Binding Items, Mode=OneWay}" SelectedItem="{Binding SelectedItem}">
...
<view:MyUserControl DataContext="{Binding SelectedItem}" />
</Window>
Now, let's say I have two items in my listbox and I have the first one selected. I fill in "foo" in the textbox of MyUserControl. Then I select the second item, and fill in "bar". The databinding works fine, and both items have the correct values set. If I then click the browse button on the first one and select a folder, it will change the textbox of the first item to the selected path. However, if I select the second item, and browse to a folder it will ALSO change the first item's textbox.
My guess is that the message attach syntax does not call the browse action on the correct Item. It disregards the datacontext (currently selected item) and just uses the first one.
What can I do about this?
I think your guess is correct; the target used for the Message.Attach is the first data context bound, and does not update when the context is changed after the user selection.
We saw a similar problem with user controls switched in a content control - the fix was to specify cal:Action.TargetWithoutContext="{Binding}" on the button.
The issue is mentioned here by Rob Eisenberg:
https://caliburnmicro.codeplex.com/discussions/257005
I have made a workaround by changing
cal:Message.Attach="[Event Click] = [Action BrowseHotFolder()]"
to
cal:Message.Attach="[Event Click] = [Action BrowseHotFolder($datacontext)]"
Now, the BrowseHotFolder-method still is called on the wrong ItemViewModel, but weirdly $datacontext passes the correct ItemViewModel. In the method itself I now do:
public void BrowsePath(ItemViewModel context)
{
context.Path = _folderBrowsingService.Browse();
}
This is a workaround, but solves the problem.
My WPF form is using multiple user controls. These represents unique forms. I need to give navigational kind of functionality.
Once a user clicks on EDIT button which is on UserControl A, I want to bind DataContext of UserControl B and Open this user control on master form.
This master form contains both the user control. How can I do that?
This is how my code looks like
<Window>
<TabControl Background="Transparent">
<TabItem Header="View Registration" Background="Transparent">
<my:BulkPersonRegistration x:Name="BulkPersonRegistrationForm" />
</TabItem>
<TabItem Header="Add/Update Person" Background="Transparent">
<my:PersonManager x:Name="PersonManagerForm" />
</TabItem>
</TabControl>
</Window>
BulkPersonRegistration user control calls a function for data binding to display a datagrid. Upon binding of a datagrid, edit button appears after every row. When user clicks edit button, I want PersonManager form to be binded by the object of edited row.
Problem is, BulkPersonRegistration form is not aware of PersonManagerForm. So I need a way to find out Parent form of BulkPersonRegistration i.e. Owner form and then find Personmanager form and finally setting it's data context. I think all of this needs to be done on BulkRegistration form. I would prefer doing this on Window rather than user control.
Please suggest.
You can create a layout along these lines...
<DockPanel>
<Button DockPanel.Dock="Top"
Content ="EDIT"
Height="30"
Width="150"
Click="ButtonBase_OnClick"
/>
<TextBlock DockPanel.Dock="Top" Name="Control1" DataContext="{Binding}" Background="Yellow"/>
<TextBlock DockPanel.Dock="Top" Name="Control2" Visibility="Collapsed" Background="Green"/>
<Rectangle DockPanel.Dock="Top"></Rectangle>
</DockPanel>
And in the click handler, you can set the data context of the second control...
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
Control2.DataContext = Control1.DataContext;
Control2.Visibility = Visibility.Visible;
}
This snippet makes the second control visible and makes the first control share its data context with the second control. You can actually set the control's data context to anything, but this snippet assumes it's the same data context. Setting the control's visibility will make it 'open'.
Hi You can achieve this simply by Binding using ElementName like this
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<local:UserControl1 x:Name="uc1"/>
<local:UserControl2 Grid.Row="1" DataContext="{Binding DataContext, ElementName=uc1}"/>
</Grid>
I hope this will help.
In Silverlight, I have a DataTemplate which is bound to an object which contains a property which holds a UserControl.
Within the DataTemplate, I want to bind to the property which holds the UserControl so that the UserControl is displayed as part of the DataTemplate.
At the moment, I am using an ItemsControl and binding the ItemsSource to the property containing the UserControl and this is working, however, the UserControl is not filling the available space and this is making me wonder whether there is a better way of doing this.
Thanks for any help.
Martyn.
EDIT: As requested some XAML:
<DataTemplate x:Key="ContentTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<TextBlock Text="Large Content" Grid.Row="0"/>
<ItemsControl ItemsSource="{Binding Contents}" Grid.Row="1" MinHeight="200" MinWidth="300" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
</Grid>
</DataTemplate>
Where, the Contents property being bound to is as follows:
private UserControl _contents;
public UserControl Contents
{
get {return _contents;}
set
{
_contents = value;
NotifyPropertyChanged("Contents");
}
}
Don't know why you're using an ItemsControl to show the content, maybe if you try it with a ContentControl.
<ContentControl Content="{Binding Contents}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" ...
Note the the properties HorizontalContentAlignment and VerticalContentAlignment, these properties sets the alignament of the control content, so if you set them to "Stretch" then the content should fit all the available space.
I got a wpf application.
I want all my data grids in application to have a set of buttons above them.
Tried to use decorator and adorner without success(the dataGrid stopped showing rows)
Any suggestions?
Given that you're wanting to have functionality behind the toolbox buttons (which I assume will require a reference to the grid) it probably makes sense to inherit from a HeaderedContentControl for this. This does mean that you can put any content in the control, but it would be possible to put override the metadata to add validation for this.
Anywhere, here's the xaml:
<!-- ToolBoxGridControl.xaml -->
<HeaderedContentControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WpfApplication3.ToolBoxGridControl">
<HeaderedContentControl.Header>
<StackPanel Orientation="Horizontal">
<Button/>
<Button/>
<Button/>
</StackPanel>
</HeaderedContentControl.Header>
<HeaderedContentControl.Template>
<ControlTemplate TargetType="HeaderedContentControl">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" Content="{TemplateBinding Header}"/>
<ContentControl Grid.Row="1" Content="{TemplateBinding Content}"/>
</Grid>
</ControlTemplate>
</HeaderedContentControl.Template>
</HeaderedContentControl>
And the simple code-behind (where you can put your toolbox implementation).
public partial class ToolBoxGridControl : HeaderedContentControl
{
private DataGrid DataGrid { get { return (DataGrid)Content; } }
public ToolBoxGridControl()
{
this.InitializeComponent();
}
}
To actually use, you can just add the following to your XAML with your data grid
<local:ToolBoxGridControl>
<DataGrid/>
</local:ToolBoxGridControl>
This is my current Scenario: I have several UserControls inside different TabItems on a single TabControl in a WPF Window. Something Like:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="S.C.A.R" WindowState="Maximized">
<TabControl Name="MainTabControl">
<TabItem Name="TabOps">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition Height="20"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="30"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Local:ServiceList Height="Auto" CanInsert="True" CanCollapse="True" Grid.ColumnSpan="3" x:Name="SL" RecordState="Edit"/>
<Local:ReservationList CanCollapse="True" Grid.Row="1" RecordState="Edit" x:Name="RL"/>
<Local:DriverList CanDelete="False" CanInsert="False" CanCollapse="True" Grid.Row="1" Grid.Column="2" RecordState="Edit" x:Name="DL"/>
<Local:CustomerForm CanDelete="False" CanInsert="False" Grid.Row="2" Grid.ColumnSpan="3" RecordState="View" x:Name="CL"/>
</Grid>
</TabItem>
<TabItemItem Name="TabCodes">
<Local:CustomerList x:Name="CustomerCRUD" RecordState="View"/>
</TabItem>
<Button Grid.Row="1" Content="TEST" Click="Button_Click"/>
</Grid>
</Border>
</Window>
Sorry for the indentation. For some reason I can't get the code properly indented here :(
What I need to do is to determine (preferably in the TabControl.Load Method, which of my different UserControls are currently visible. I need to do this in a dynamic way, I cannot hardcode the relationship between the TabItems and their children, something like:
if (TabControl.SelectedItem is XXXX)... is not possible here, because this is a Dynamic UI and I have no way to know which controls are there up front.
I've been digging a little bit and found out that the TabItem controls do not appear in the Visual tree of their "children". I only see a ContentPresenter, and then the TabControl itself. It looks like the tabItems do not "contain" their own content, so I could not, for example, do a FindAncestor to the Tab Items.
Another interesting fact is that the Loaded event of my usercontrols is being called on startup. Regardless of whether or not they're visible on screen.
An ideal scenario will be to find an event that is only fired on my Usercontrols when the TabItem they are under gets selected.
Appreciate any ideas. Thanks in advance
You should be able to leverage the VisualTreeHelper and consequentrly this answer on SO to provide the TabItem.Content returned object and look for the your specified type, UserControl in this instance.
NOTE:
For additional details please see the comments which transpired in the SO's question.