2010-04-15 3 views
3

동적 리소스는 정말 동적입니까? DynamicResource를 정의하면 런타임까지 리소스로 변환되지 않는 표현식이 생성된다는 것을 알았지 만, 일단 작성한이 동적 리소스가 일단 "정적"인지 여부는 아직 이해하지 못했습니다.DynamicResources는 컨텍스트 메뉴에서 어떻게 빌드되고 사용됩니까

예를 들어, dynamicresource를 통해 상황에 맞는 메뉴를 만들면 바인딩 된 경우에도 런타임에 액세스 할 때 정적으로 생성되는 메뉴 항목이 있습니까?

그렇다면 어떻게하면 XAML에서 동적 컨텍스트 메뉴를 만들 수 있습니까?

+0

여기에 좀 더 자세한 내용을 추가했습니다 ... 나는 dynamicresources가 어떻게 만들어 졌는지에 대한 몇 가지 구체적인 내용을 찾고 있는데, 또한 수화 후에 정적 인 문제를 해결하는 방법을 찾고 있습니다. – miguel

답변

18

이것은 매우 복잡한 주제이기 때문에 WPF에는 매우 다양한 종류가 있습니다. 간단한 예제로 시작하여 필요한 기본 개념을 이해하고 ContextMenu를 동적으로 업데이트 및/또는 교체 할 수있는 다양한 방법을 설명하고 DynamicResource를 그림에 적용하는 방법을 설명합니다.

초기 예 : 동적으로의 ContextMenu가 정적 리소스

를 통해 참조하여 갱신이의 다음 당신이 있다고 가정 해 봅시다 :

<Window> 
    <Window.Resources> 
    <ContextMenu x:Key="Vegetables"> 
     <MenuItem Header="Broccoli" /> 
     <MenuItem Header="Cucumber" /> 
     <MenuItem Header="Cauliflower" /> 
    </ContextMenu> 
    </Window.Resources> 

    <Grid> 
    <Ellipse ContextMenu="{StaticResource Vegetables}" /> 
    <TextBox ContextMenu="{StaticResource Vegetables}" ... /> 
    ... 
    </Grid> 
</Window> 

**

지금은 StaticResource의 사용을합니다.

이 XAML는 :

  • 세 개의 메뉴 아이템과 ContextMenu 객체를 구성하고 Window.Resources
  • 에 추가의 ContextMenu
  • 참조하여 타원 객체를 구성 기호가있는 텍스트 상자 객체를 구성 ContextMenu에 대한 참조

Ellipse와 TextBox 모두 동일한 ContextMenu에 대한 참조를 가지므로 ContextMenu를 업데이트하면 op가 변경됩니다 각각에 사용할 수있는 예를 들어, 다음은 버튼을 클릭하면 "Carrots"를 메뉴에 추가합니다.

public void Button_Click(object sender, EventArgs e) 
{ 
    var menu = (ContextMenu)Resources["Vegetables"]; 
    menu.Items.Add(new MenuItem { Header = "Carrots" }); 
} 

이 점에서 모든 ContextMenu는 동적입니다. 항목은 언제든지 수정할 수 있으며 변경 사항은 즉시 적용됩니다. ContextMenu가 실제로 화면에서 열렸을 때에도 마찬가지입니다.

의 ContextMenu는 동적

단일 ContextMenu 객체가 동적 데이터 바인딩 응답이다하는 또 다른 방법을 통해 데이터 바인딩 업데이트. 대신 각각의 메뉴 아이템을 설정하는 당신은 예를 들어, 컬렉션에 바인딩 할 수 있습니다 :이 VegetableList는 ObservableCollection에 또는 INotifyCollectionChanged 인터페이스를 구현 다른 유형으로 선언되어 있다고 가정

<Window.Resources> 
    <ContextMenu x:Key="Vegetables" ItemsSource="{Binding VegetableList}" /> 
</Window.Resources> 

. 콜렉션을 변경하면 ContextMenu가 열려 있어도 즉시 업데이트됩니다. 변경 사항은 최종 사용자에 의해 만들어 질 수 있도록 당신은 또한 ListView를, 데이터 그리드 등으로 야채 목록을 바인딩 할 수 있습니다 : 컬렉션을 업데이트 이런 종류의 코드에서 할 필요가 없다는 것을

public void Button_Click(object sender, EventArgs e) 
{ 
    VegetableList.Add("Carrots"); 
} 

참고 : 예를 들면. 이 변경 사항은 ContextMenu에도 표시됩니다.또한 완전히 다른의 ContextMenu와 항목의의 ContextMenu를 대체 할 수있는 코드

를 사용

전환 ContextMenus. 예를 들어

<Window> 
    <Window.Resources> 
    <ContextMenu x:Key="Vegetables"> 
     <MenuItem Header="Broccoli" /> 
     <MenuItem Header="Cucumber" /> 
    </ContextMenu> 
    <ContextMenu x:Key="Fruits"> 
     <MenuItem Header="Apple" /> 
     <MenuItem Header="Banana" /> 
    </ContextMenu> 
    </Window.Resources> 

    <Grid> 
    <Ellipse x:Name="Oval" ContextMenu="{StaticResource Vegetables}" /> 
    ... 
    </Grid> 
</Window> 

메뉴는 다음과 같은 코드로 대체 될 수있다 : 대신 기존의 ContextMenu를 수정 우리는 완전히 다른의 ContextMenu 전환된다

public void Button_Click(object sender, EventArgs e) 
{ 
    Oval.ContextMenu = (ContextMenu)Resources.Find("Fruits"); 
} 

참고. 이 상황에서 두 ContextMenus는 창을 처음 구성 할 때 즉시 작성되지만 Fruits 메뉴는 전환 될 때까지 사용되지 않습니다. 당신은 당신이 XAML에서 그 일을 대신 Button_Click 핸들러를 구성 할 수 필요한 때까지 과일 메뉴를 구성하지 않도록하려면

:이 예에서

public void Button_Click(object sender, EventArgs e) 
{ 
    Oval.ContextMenu = 
    new ContextMenu { ItemsSource = new[] { "Apples", "Bananas" } }; 
} 

때마다 당신은 버튼을 클릭 새로운 ContextMenu가 생성되고 타원에 지정됩니다. Window.Resources에 정의 된 모든 ContextMenu는 여전히 존재하지만 사용되지 않습니다 (다른 컨트롤이 사용하지 않는 한). DynamicResource 사용

DynamicResource를 사용

전환 ContextMenus은 명시 적으로 그것을 코드를 지정하지 않고 ContextMenus 사이를 전환 할 수 있습니다. 이 XAML은 타원의 ContextMenu 속성을 업데이트 할 사전을 수정하는 대신 정적 리소스의 DynamicResource를 사용

<Window> 
    <Window.Resources> 
    <ContextMenu x:Key="Vegetables"> 
     <MenuItem Header="Broccoli" /> 
     <MenuItem Header="Cucumber" /> 
    </ContextMenu> 
    </Window.Resources> 

    <Grid> 
    <Ellipse ContextMenu="{DynamicResource Vegetables}" /> 
    ... 
    </Grid> 
</Window> 

때문에 예를 들면 다음과 같습니다. 예를 들어 : 사전이 조회가 완료 될 때

public void Button_Click(object sender, EventArgs e) 
{ 
    Resources["Vegetables"] = 
    new ContextMenu { ItemsSource = new[] {"Zucchini", "Tomatoes"} }; 
} 

여기서 중요한 개념은 정적 리소스 컨트롤 대 그 DynamicResource입니다. 위의 예제에서 StaticResource를 사용하는 경우 Resources["Vegetables"]에 할당하면 타원의 ContextMenu 속성이 업데이트되지 않습니다.

한편, Items 컬렉션을 변경하거나 데이터 바인딩을 통해 ContextMenu 자체를 업데이트하는 경우 DynamicResource를 사용하든 StaticResource를 사용하든 관계가 없습니다. ContextMenu에 대한 모든 변경 사항은 즉시 볼 수 있습니다.

<ContextMenu x:Key="SelfUpdatingMenu"> 
    <MenuItem Header="Delete" IsEnabled="{Binding IsDeletable}" /> 
    ... 
</ContextMenu> 

이를 :

즉 마우스 오른쪽 버튼으로 클릭입니다

항목의 속성을 기반으로의 ContextMenu를 업데이트하는 가장 좋은 방법은 데이터 바인딩을 사용하여 개별의 ContextMenu 항목을 업데이트하면 데이터 바인딩을 사용하는 것입니다 항목에 IsDeletable 플래그가 설정되어 있지 않으면 "삭제"메뉴 항목이 자동으로 회색으로 표시됩니다. 이 경우에는 코드가 필요하지 않습니다.

당신이 그것을 밖으로 어지는 단순히 대신 대신의 IsEnabled의 설정 가시성 항목을 숨기려면 :

<MenuItem Header="Delete" 
      Visibility="{Binding IsDeletable, Converter={x:Static BooleanToVisibilityConverter}}" /> 

당신이 추가/데이터를 기반으로의 ContextMenu에서 항목을 제거하려면, 당신은 바인딩 할 수 있습니다 CompositeCollection를 사용하여. 구문은 조금 더 복잡하지만 여전히 아주 간단하다 :

<ContextMenu x:Key="MenuWithEmbeddedList"> 
    <ContextMenu.ItemsSource> 
    <CompositeCollection> 
     <MenuItem Header="This item is always present" /> 
     <MenuItem Header="So is this one" /> 
     <Separator /> <!-- draw a bar --> 
     <CollectionContainer Collection="{Binding MyChoicesList}" /> 
     <Separator /> 
     <MenuItem Header="Fixed item at bottom of menu" /> 
    </CompositeCollection> 
    </ContextMenu.ItemsSource> 
</ContextMenu> 

는 가정 "MyChoicesList"는 것이 컬렉션에 업데이트 ObservableCollection에 (또는 INotifyCollectionChanged를 구현하는 다른 클래스), 제거 된 추가 된 항목//입니다 ContextMenu에 즉시 표시됩니다.

당신이 데이터를 바인딩 사용하여의 ContextMenu 항목을 제어해야하는 경우에 가능한 모든 데이터가

바인딩하지 않고 개인의 ContextMenu 항목을 업데이트. 그것들은 매우 잘 작동하고 거의 완벽하며 코드를 크게 단순화합니다. 데이터 바인딩을 작동시킬 수없는 경우에만 코드를 사용하여 메뉴 항목을 업데이트하는 것이 좋습니다. 이 경우 ContextMenu.Opened 이벤트를 처리하고이 이벤트 내에서 업데이트를 수행하여 ContextMenu를 빌드 할 수 있습니다. 이 코드

<ContextMenu x:Key="Vegetables" Opened="Vegetables_Opened"> 
    <MenuItem Header="Broccoli" /> 
    <MenuItem Header="Green Peppers" /> 
</ContextMenu> 

: 당신은 데이터 바인딩을 사용하고있는 경우

public void Vegetables_Opened(object sender, RoutedEventArgs e) 
{ 
    var menu = (ContextMenu)sender; 
    var data = (MyDataClass)menu.DataContext 

    var oldCarrots = (
    from item in menu.Items 
    where (string)item.Header=="Carrots" 
    select item 
).FirstOrDefault(); 

    if(oldCarrots!=null) 
    menu.Items.Remove(oldCarrots); 

    if(ComplexCalculationOnDataItem(data) && UnrelatedCondition()) 
    menu.Items.Add(new MenuItem { Header = "Carrots" }); 
} 

는 또한이 코드는 단순히 menu.ItemsSource을 변경할 수 있습니다 예를 들어. 트리거

일반적으로 ContextMenus를 업데이트하는 데 사용되는 또 다른 기술을 사용하여

전환 ContextMenus는 기본 컨텍스트 메뉴를 트리거 조건에 따라 사용자 정의 컨텍스트 메뉴 사이를 전환 할 수 트리거 또는 DataTrigger를 사용하는 것입니다. 이렇게하면 데이터 바인딩을 사용하지만 부분을 업데이트하지 않고 메뉴 전체를 교체해야하는 상황을 처리 할 수 ​​있습니다.

<ControlTemplate ...> 

    <ControlTemplate.Resources> 
    <ContextMenu x:Key="NormalMenu"> 
     ... 
    </ContextMenu> 
    <ContextMenu x:Key="AlternateMenu"> 
     ... 
    </ContextMenu> 
    </ControlTemplate.Resources> 

    ... 

    <ListBox x:Name="MyList" ContextMenu="{StaticResource NormalMenu}"> 

    ... 

    <ControlTemplate.Triggers> 
    <Trigger Property="IsSpecialSomethingOrOther" Value="True"> 
     <Setter TargetName="MyList" Property="ContextMenu" Value="{StaticResource AlternateMenu}" /> 
    </Trigger> 
    </ControlTemplate.Triggers> 
</ControlTemplate> 

가 NormalMenu 및 AlternateMenu 모두에서 개별 항목을 제어하는 ​​데이터 바인딩을 사용하는 것이 여전히 가능이 시나리오에서는 다음과 같습니다

이 같은 모습의 그림입니다. 컨텍스트 메뉴 ContextMenu에서 사용되는 리소스는 당신이 그들을 해제 할 수 있습니다 RAM을 유지하기위한 비용이있는 경우 메뉴가

을 닫을 때의 ContextMenu 자원을 해제

. 데이터 바인딩을 사용하는 경우 메뉴가 닫힐 때 DataContext가 제거되므로이 작업은 자동으로 수행됩니다. 대신 코드를 사용하는 경우 Opened 이벤트에 대한 응답으로 생성 한 내용을 할당 해제하려면 ContextMenu에서 Closed 이벤트를 catch해야 할 수 있습니다.

당신이 XAML 코드 싶지만이 필요한 경우를 제외하고는로드하지 않으려는 매우 복잡한의 ContextMenu이있는 경우

는 두 가지 기본 기술을 사용할 수 있습니다 XAML에서의 ContextMenu의 지연 건축 :

  1. 별도의 ResourceDictionary에 넣습니다. 필요한 경우 ResourceDictionary를로드하고 MergedDictionaries에 추가합니다. DynamicResource를 사용하는 한 병합 된 값이 선택됩니다.

  2. ControlTemplate 또는 DataTemplate에 넣습니다. 템플릿은 템플릿이 처음 사용될 때까지 실제로 인스턴스화되지 않습니다. 포함하는 템플릿이 인스턴스화 또는 사전 병합 된 경우에만 - 그 자체로 이러한 기술 중 어느 것도 상황에 맞는 메뉴가 을 열 때 발생하는 부하의 원인이됩니다 그러나

. 이를 수행하려면 빈 ItemsSource가있는 ContextMenu를 사용하고 OpenSound 이벤트에 ItemsSource를 할당해야합니다. 폐쇄 이벤트에

var dict = (ResourceDictionary)Application.LoadComponent(...); 
menu.ItemsSource = dict["ComplexMenuContents"]; 

이 코드 :

열린 이벤트에서이 코드를

<ResourceDictionary ...> 
    <x:Array x:Key="ComplexContextMenuContents"> 
    <MenuItem Header="Broccoli" /> 
    <MenuItem Header="Green Beans" /> 
    ... complex content here ... 
    </x:Array> 
</ResourceDictionary> 

다음 ItemsSource의 값은 별도의 파일에있는 ResourceDictionary에서로드 할 수 있습니다 그러나

menu.ItemsSource = null; 

사실 x : Array가 하나 뿐인 경우 ResourceDictionary를 건너 뛸 수도 있습니다. 당신의 XAML의 바깥 쪽 요소가 X 인 경우 : 배열을 개설 이벤트 코드는 간단하다 : 중요한 개념

DynamicResource의

menu.ItemsSource = Application.LoadComponent(....) 

요약 만있는 자원 사전로드되어 있으며 내용에 따라 값을 전환하여 사용 사전의 내용을 업데이트 할 때 DynamicResource는 자동으로 등록 정보를 업데이트합니다. StaticResource는 XAML이로드 될 때만이를 읽습니다.

DynamicResource를 사용하든 StaticResource를 사용하든 관계없이 메뉴를 열 때이로드되지 않으면 ContextMenu가 으로 만들어집니다.

ContextMenus는 데이터 바인딩이나 코드를 사용하여 조작 할 수있어 매우 효과적이며 변경 사항이 즉시 적용됩니다.

대부분의 경우 코드가 아닌 데이터 바인딩을 사용하여 ContextMenu를 업데이트해야합니다.

코드, 트리거 또는 DynamicResource를 사용하여 메뉴를 완전히 대체 할 수 있습니다.

메뉴가 열려있을 때만 내용을 RAM에로드해야하는 경우 Opened 이벤트의 별도 파일에서 내용을로드하고 Closed 이벤트에서 내용을 지울 수 있습니다.

+0

좋은 물건! 매우 감사합니다. – miguel

+0

이것보다 더 철저한 대답은 상상할 수 없습니다. +1. – Charlie

관련 문제