Przyciski ponad obrazkiem z względnym położeniem

1

Potrzebuję dodać do programu opcję wyświetlania obrazka, ponad którym będą znajdowały się w określonych pozycjach przyciski przykrywające jego określone fragmenty. Chodzi o pokazywanie obrazku pewnego urządzenia i nad przyciskami ze zdjęcia mają znaleźć się przyciski (Buttony), które będą przypisywać funkcje do określonych przycisków urządzenia. Nie zamotałem?

Testowo zrobiłem sobie klasę:

class HardwareButton
{
    public int Left { get; set; }
    public int Top { get; set; }
    public string Text { get; set; }
}

(tam docelowo powinno być jeszcze width i height, ale na razie zostawmy)

oraz konwerter:

public class MarginConverter : IValueConverter
{

    public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        HardwareButton v = (HardwareButton)value;
        return new Thickness(v.Left, v.Top, 0, 0);
    }

    public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }
}

I to w rezultacie daje mi całkiem fajną koncepcję:

<ItemsControl x:Name="con">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Content="{Binding Text}" Margin="{Binding Converter={StaticResource marginConverter}}" Width="20" Height="20" HorizontalAlignment="Left" Click="Button_Click" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

0ada766bfe.png

Kolekcja przycisków się binduje z ItemsControlem, pojedyncze przyciski są przedstawiane jako buttony, marginesy są poprawnie rozpoznawane... Tylko jeden problem - co będzie, kiedy zmienię rozmiar okienka? Left i Top nie są przecież względne, tylko absolutne wzorem kontenera. Więc hipotetyczny (wyżej przedstawiono przykład ;-)) obrazek w tle mi się zwiększy/zmniejszy, a przyciski znajdą się na błędnych pozycjach.

Więc pytanie jest - jak zrobić mniej więcej to samo, ale aby działało także dla zmienionych rozmiarów okienka/kontenera?

0

Podejrzewam, że będzie trzeba wyliczać aktualną szerokość i wysokość okna przy resizowaniu [właściwości (ActualWidth, ActualHeight) oraz event (SizeChanged)] i podzielić różnicę przez 2, a wyniki pododawać do właściwości Left i Top każdego z buttonów.

PS: buttony można sobie zrobić ładne okrągłe i przezroczyste wtedy efekt będzie super.

Edit.
Drugą opcją jest podzielenie okna na konkretną ilość kolumn i wierszy, a szerokość/wysokość dopasować do obrazka (np <ColumnDefinition Width="0.3" />) i buttony osadzić już w poprawnie ustawionej kolumnie/wierszu. Teraz przy resize okna kolumny i wiersze też się same dostosują a tym samym buttony powędrują z nimi. Znikają nam też właściwości Left i Top bo one już w tym rozwiązaniu się nie przydadzą.

0

Drugą opcją jest podzielenie okna na konkretną ilość kolumn i wierszy, a szerokość/wysokość dopasować do obrazka (np <ColumnDefinition Width="0.3" />) i buttony osadzić już w poprawnie ustawionej kolumnie/wierszu. Teraz przy resize okna kolumny i wiersze też się same dostosują a tym samym buttony powędrują z nimi. Znikają nam też właściwości Left i Top bo one już w tym rozwiązaniu się nie przydadzą.

Niestety - zarówno ilość przycisków, ich rozmieszczenie, jak i rysunek pod spodem są zmienne (aplikacja jest dostosowywalna do kilku urządzeń). Grid musiałby się dostosowywać do zbindowanej kolekcji, co będzie chyba równie trudne.

UnlimitedPL napisał(a):

Podejrzewam, że będzie trzeba wyliczać aktualną szerokość i wysokość okna przy resizowaniu [właściwości (ActualWidth, ActualHeight) oraz event (SizeChanged)] i podzielić różnicę przez 2, a wyniki pododawać do właściwości Left i Top każdego z buttonów.

Zacząłem iść w inną stronę, używając TranslateTransform i rezultat jest w połowie dobry - działa wszystko na rozszerzanie w bok - znaczy TranslateTransform jest względne ;)
W podobny sposób nie mam jeszcze koncepcji jak można by to samo osiągnąć w przypadku osi Y.

Użyłem MultiConverter i MultiBinding, któremu mogę przekazać kilka danych, także tych z innych kontrolek, np. z okienka jako takiego.

(uwaga, horror)

<ItemsControl x:Name="con" VerticalAlignment="Top">
    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="local:HardwareButton">
            <Button Content="{Binding Text}" Margin="0" Width="20" Height="20" Click="Button_Click">
                <Button.RenderTransform>
                    <TranslateTransform X="{Binding Left}">
                        <TranslateTransform.Y>
                            <MultiBinding Converter="{StaticResource topConverter}">
                                <Binding Path="Top" />
                                <Binding Path="ActualHeight" ElementName="MWindow" />
                            </MultiBinding>
                        </TranslateTransform.Y>
                    </TranslateTransform>
                </Button.RenderTransform>
            </Button>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Teraz mój TopConverter dostaje "Top" z przycisku, który ma się pojawić, jak i aktualną wysokość okienka.

public class TopConverter : IMultiValueConverter
{
    private const int MinHeight = 350;

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        int top = (int)values[0];
        double actualHeight = (double)values[1];

        return (actualHeight - MinHeight + top);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

A w ogóle to tam nie powinna być wysokość okienka, ale raczej obrazka, ale mniejsa o to.

Nie mam teraz pomysłu jak uzależnić przesunięcie w pionie od wysokości tak, aby to miało sens.

1

Po co tyle kombinować? Pewnie się da tym sposobem ale bez kombinowania można po prostu:

 private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            var differenceW = ActualWidth - MinWidth;
            var differenceH = ActualHeight - MinHeight;

            ctrlButton.Margin = new Thickness(differenceW / 2, differenceH / 2, 0, 0);
        }

oczywiście jeśli MVVM to binduj sobie Acutal'e do ViewModelu i jak zmieni się rozmiar okna to zamiast mojego buttona zrób sobie foreach'a ze swoją kolekcją buttonów poustawiaj Left, Top i przez konwerter z Twojego pierwszego postu do Margina w buttonie

0

@up nie odświerzyłem strony i tw postu nie widzialem :P
Po co tak kombinować pisac konwerter itd, możesz napisać klase dziedziczaca po Buttom, nadpisać konstruktor by ustawiał wysokość i szerokość, na left top ustawić setery i getery sterujące marginesem i dodać fukcje skalujące pozycje i rozmiar. Potem w mainwindow na pisać i dodać event na zmiane rozmiaru, który dla każdego guzika zaktaulizuje/ poprawi pozycje :).

0

Heh, zarówno @UnlimitedPL, jak i @topik92 mi tutaj proponują event na zmianę rozmiaru, a ja się uparłem z eventu tego nie korzystać ;)

Skrzyżowałem rozwiązania podawane powyżej - zmodyfikowałem TopConverter w taki sposób:

public class TopConverter : IMultiValueConverter
{
    private const int MinHeight = 350;

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        int top = (int)values[0];
        double actualHeight = (double)values[1];

        return (actualHeight - MinHeight) / 2 + top;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

W ten sposób elementy zachowują swoją pozycję przy przesuwaniu góra/dół. Warto mieć na uwadze, że aby było prościej to po prostu wielkość obrazka (wypełnienia) jest niezmienna. I zasadniczo działa jak powinno przy wykorzystaniu tej przekombinowanej TranslateTransform. Zobaczymy jak wyjdzie w finalnym kodzie, co prawda.

1 użytkowników online, w tym zalogowanych: 0, gości: 1